summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/os/sunpm.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/os/sunpm.c')
-rw-r--r--usr/src/uts/common/os/sunpm.c9096
1 files changed, 9096 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/sunpm.c b/usr/src/uts/common/os/sunpm.c
new file mode 100644
index 0000000000..d209375e29
--- /dev/null
+++ b/usr/src/uts/common/os/sunpm.c
@@ -0,0 +1,9096 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * sunpm.c builds sunpm.o "power management framework"
+ * kernel-resident power management code. Implements power management
+ * policy
+ * Assumes: all backwards compat. device components wake up on &
+ * the pm_info pointer in dev_info is initially NULL
+ *
+ * PM - (device) Power Management
+ *
+ * Each device may have 0 or more components. If a device has no components,
+ * then it can't be power managed. Each component has 2 or more
+ * power states.
+ *
+ * "Backwards Compatible" (bc) devices:
+ * There are two different types of devices from the point of view of this
+ * code. The original type, left over from the original PM implementation on
+ * the voyager platform are known in this code as "backwards compatible"
+ * devices (PM_ISBC(dip) returns true).
+ * They are recognized by the pm code by the lack of a pm-components property
+ * and a call made by the driver to pm_create_components(9F).
+ * For these devices, component 0 is special, and represents the power state
+ * of the device. If component 0 is to be set to power level 0 (off), then
+ * the framework must first call into the driver's detach(9E) routine with
+ * DDI_PM_SUSPEND, to get the driver to save the hardware state of the device.
+ * After setting component 0 from 0 to a non-zero power level, a call must be
+ * made into the driver's attach(9E) routine with DDI_PM_RESUME.
+ *
+ * Currently, the only way to get a bc device power managed is via a set of
+ * ioctls (PM_DIRECT_PM, PM_SET_CURRENT_POWER) issued to /dev/pm.
+ *
+ * For non-bc devices, the driver describes the components by exporting a
+ * pm-components(9P) property that tells how many components there are,
+ * tells what each component's power state values are, and provides human
+ * readable strings (currently unused) for each component name and power state.
+ * Devices which export pm-components(9P) are automatically power managed
+ * whenever autopm is enabled (via PM_START_PM ioctl issued by pmconfig(1M)
+ * after parsing power.conf(4)).
+ * For these devices, all components are considered independent of each other,
+ * and it is up to the driver to decide when a transition requires saving or
+ * restoring hardware state.
+ *
+ * Each device component also has a threshold time associated with each power
+ * transition (see power.conf(4)), and a busy/idle state maintained by the
+ * driver calling pm_idle_component(9F) and pm_busy_component(9F).
+ * Components are created idle.
+ *
+ * The PM framework provides several functions:
+ * -implement PM policy as described in power.conf(4)
+ * Policy is set by pmconfig(1M) issuing pm ioctls based on power.conf(4).
+ * Policies consist of:
+ * -set threshold values (defaults if none provided by pmconfig)
+ * -set dependencies among devices
+ * -enable/disable autopm
+ * -turn down idle components based on thresholds (if autopm is enabled)
+ * (aka scanning)
+ * -maintain power states based on dependencies among devices
+ * -upon request, or when the frame buffer powers off, attempt to turn off
+ * all components that are idle or become idle over the next (10 sec)
+ * period in an attempt to get down to an EnergyStar compliant state
+ * -prevent powering off of a device which exported the
+ * pm-no-involuntary-power-cycles property without active involvement of
+ * the device's driver (so no removing power when the device driver is
+ * not attached)
+ * -provide a mechanism for a device driver to request that a device's component
+ * be brought back to the power level necessary for the use of the device
+ * -allow a process to directly control the power levels of device components
+ * (via ioctls issued to /dev/pm--see usr/src/uts/common/io/pm.c)
+ * -ensure that the console frame buffer is powered up before being referenced
+ * via prom_printf() or other prom calls that might generate console output
+ * -maintain implicit dependencies (e.g. parent must be powered up if child is)
+ * -provide "backwards compatible" behavior for devices without pm-components
+ * property
+ *
+ * Scanning:
+ * Whenever autopm is enabled, the framework attempts to bring each component
+ * of each device to its lowest power based on the threshold of idleness
+ * associated with each transition and the busy/idle state of the component.
+ *
+ * The actual work of this is done by pm_scan_dev(), which cycles through each
+ * component of a device, checking its idleness against its current threshold,
+ * and calling pm_set_power() as appropriate to change the power level.
+ * This function also indicates when it would next be profitable to scan the
+ * device again, and a new scan is scheduled after that time.
+ *
+ * Dependencies:
+ * It is possible to establish a dependency between the power states of two
+ * otherwise unrelated devices. This is currently done to ensure that the
+ * cdrom is always up whenever the console framebuffer is up, so that the user
+ * can insert a cdrom and see a popup as a result.
+ *
+ * The dependency terminology used in power.conf(4) is not easy to understand,
+ * so we've adopted a different terminology in the implementation. We write
+ * of a "keeps up" and a "kept up" device. A relationship can be established
+ * where one device keeps up another. That means that if the keepsup device
+ * has any component that is at a non-zero power level, all components of the
+ * "kept up" device must be brought to full power. This relationship is
+ * asynchronous. When the keeping device is powered up, a request is queued
+ * to a worker thread to bring up the kept device. The caller does not wait.
+ * Scan will not turn down a kept up device.
+ *
+ * Direct PM:
+ * A device may be directly power managed by a process. If a device is
+ * directly pm'd, then it will not be scanned, and dependencies will not be
+ * enforced. * If a directly pm'd device's driver requests a power change (via
+ * pm_raise_power(9F)), then the request is blocked and notification is sent
+ * to the controlling process, which must issue the requested power change for
+ * the driver to proceed.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/callb.h> /* callback registration during CPR */
+#include <sys/conf.h> /* driver flags and functions */
+#include <sys/open.h> /* OTYP_CHR definition */
+#include <sys/stat.h> /* S_IFCHR definition */
+#include <sys/pathname.h> /* name -> dev_info xlation */
+#include <sys/ddi_impldefs.h> /* dev_info node fields */
+#include <sys/kmem.h> /* memory alloc stuff */
+#include <sys/debug.h>
+#include <sys/archsystm.h>
+#include <sys/pm.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sunndi.h>
+#include <sys/sunpm.h>
+#include <sys/epm.h>
+#include <sys/vfs.h>
+#include <sys/mode.h>
+#include <sys/mkdev.h>
+#include <sys/promif.h>
+#include <sys/consdev.h>
+#include <sys/esunddi.h>
+#include <sys/modctl.h>
+#include <sys/fs/ufs_fs.h>
+#include <sys/note.h>
+#include <sys/taskq.h>
+#include <sys/bootconf.h>
+#include <sys/reboot.h>
+#include <sys/spl.h>
+#include <sys/disp.h>
+#include <sys/sobject.h>
+#include <sys/sunmdi.h>
+
+
+/*
+ * PM LOCKING
+ * The list of locks:
+ * Global pm mutex locks.
+ *
+ * pm_scan_lock:
+ * It protects the timeout id of the scan thread, and the value
+ * of autopm_enabled. This lock is not held concurrently with
+ * any other PM locks.
+ *
+ * pm_clone_lock: Protects the clone list and count of poll events
+ * pending for the pm driver.
+ * Lock ordering:
+ * pm_clone_lock -> pm_pscc_interest_rwlock,
+ * pm_clone_lock -> pm_pscc_direct_rwlock.
+ *
+ * pm_rsvp_lock:
+ * Used to synchronize the data structures used for processes
+ * to rendezvous with state change information when doing
+ * direct PM.
+ * Lock ordering:
+ * pm_rsvp_lock -> pm_pscc_interest_rwlock,
+ * pm_rsvp_lock -> pm_pscc_direct_rwlock,
+ * pm_rsvp_lock -> pm_clone_lock.
+ *
+ * ppm_lock: protects the list of registered ppm drivers
+ * Lock ordering:
+ * ppm_lock -> ppm driver unit_lock
+ *
+ * pm_compcnt_lock:
+ * Protects count of components that are not at their lowest
+ * power level.
+ * Lock ordering:
+ * pm_compcnt_lock -> ppm_lock.
+ *
+ * pm_dep_thread_lock:
+ * Protects work list for pm_dep_thread. Not taken concurrently
+ * with any other pm lock.
+ *
+ * pm_remdrv_lock:
+ * Serializes the operation of removing noinvol data structure
+ * entries for a branch of the tree when a driver has been
+ * removed from the system (modctl_rem_major).
+ * Lock ordering:
+ * pm_remdrv_lock -> pm_noinvol_rwlock.
+ *
+ * pm_cfb_lock: (High level spin lock)
+ * Protects the count of how many components of the console
+ * frame buffer are off (so we know if we have to bring up the
+ * console as a result of a prom_printf, etc.
+ * No other locks are taken while holding this lock.
+ *
+ * pm_loan_lock:
+ * Protects the lock_loan list. List is used to record that one
+ * thread has acquired a power lock but has launched another thread
+ * to complete its processing. An entry in the list indicates that
+ * the worker thread can borrow the lock held by the other thread,
+ * which must block on the completion of the worker. Use is
+ * specific to module loading.
+ * No other locks are taken while holding this lock.
+ *
+ * Global PM rwlocks
+ *
+ * pm_thresh_rwlock:
+ * Protects the list of thresholds recorded for future use (when
+ * devices attach).
+ * Lock ordering:
+ * pm_thresh_rwlock -> devi_pm_lock
+ *
+ * pm_noinvol_rwlock:
+ * Protects list of detached nodes that had noinvol registered.
+ * No other PM locks are taken while holding pm_noinvol_rwlock.
+ *
+ * pm_pscc_direct_rwlock:
+ * Protects the list that maps devices being directly power
+ * managed to the processes that manage them.
+ * Lock ordering:
+ * pm_pscc_direct_rwlock -> psce_lock
+ *
+ * pm_pscc_interest_rwlock;
+ * Protects the list that maps state change events to processes
+ * that want to know about them.
+ * Lock ordering:
+ * pm_pscc_interest_rwlock -> psce_lock
+ *
+ * per-dip locks:
+ *
+ * Each node has these per-dip locks, which are only used if the device is
+ * a candidate for power management (e.g. has pm components)
+ *
+ * devi_pm_lock:
+ * Protects all power management state of the node except for
+ * power level, which is protected by ndi_devi_enter().
+ * Encapsulated in macros PM_LOCK_DIP()/PM_UNLOCK_DIP().
+ * Lock ordering:
+ * devi_pm_lock -> pm_rsvp_lock,
+ * devi_pm_lock -> pm_dep_thread_lock,
+ * devi_pm_lock -> pm_noinvol_rwlock,
+ * devi_pm_lock -> power lock
+ *
+ * power lock (ndi_devi_enter()):
+ * Since changing power level is possibly a slow operation (30
+ * seconds to spin up a disk drive), this is locked separately.
+ * Since a call into the driver to change the power level of one
+ * component may result in a call back into the framework to change
+ * the power level of another, this lock allows re-entrancy by
+ * the same thread (ndi_devi_enter is used for this because
+ * the USB framework uses ndi_devi_enter in its power entry point,
+ * and use of any other lock would produce a deadlock.
+ *
+ * devi_pm_busy_lock:
+ * This lock protects the integrity of the busy count. It is
+ * only taken by pm_busy_component() and pm_idle_component and
+ * some code that adjust the busy time after the timer gets set
+ * up or after a CPR operation. It is per-dip to keep from
+ * single-threading all the disk drivers on a system.
+ * It could be per component instead, but most devices have
+ * only one component.
+ * No other PM locks are taken while holding this lock.
+ *
+ */
+
+static int stdout_is_framebuffer;
+static kmutex_t e_pm_power_lock;
+static kmutex_t pm_loan_lock;
+kmutex_t pm_scan_lock;
+callb_id_t pm_cpr_cb_id;
+callb_id_t pm_panic_cb_id;
+callb_id_t pm_halt_cb_id;
+int pm_comps_notlowest; /* no. of comps not at lowest power */
+int pm_powering_down; /* cpr is source of DDI_SUSPEND calls */
+
+clock_t pm_min_scan = PM_MIN_SCAN;
+clock_t pm_id_ticks = 5; /* ticks to wait before scan during idle-down */
+
+static int pm_busop_set_power(dev_info_t *,
+ void *, pm_bus_power_op_t, void *, void *);
+static int pm_busop_match_request(dev_info_t *, void *);
+static int pm_all_to_normal_nexus(dev_info_t *, pm_canblock_t);
+
+/*
+ * Dependency Processing is done thru a seperate thread.
+ */
+kmutex_t pm_dep_thread_lock;
+kcondvar_t pm_dep_thread_cv;
+pm_dep_wk_t *pm_dep_thread_workq = NULL;
+pm_dep_wk_t *pm_dep_thread_tail = NULL;
+
+/*
+ * Autopm must be turned on by a PM_START_PM ioctl, so we don't end up
+ * power managing things in single user mode that have been suppressed via
+ * power.conf entries. Protected by pm_scan_lock.
+ */
+int autopm_enabled;
+
+/*
+ * This flag is true while processes are stopped for a checkpoint/resume.
+ * Controlling processes of direct pm'd devices are not available to
+ * participate in power level changes, so we bypass them when this is set.
+ */
+static int pm_processes_stopped;
+
+#ifdef DEBUG
+
+/*
+ * see common/sys/epm.h for PMD_* values
+ */
+uint_t pm_debug = 0;
+
+/*
+ * If pm_divertdebug is set, then no prom_printf calls will be made by
+ * PMD(), which will prevent debug output from bringing up the console
+ * frame buffer. Clearing this variable before setting pm_debug will result
+ * in PMD output going to the console.
+ *
+ * pm_divertdebug is incremented in pm_set_power() if dip == cfb_dip to avoid
+ * deadlocks and decremented at the end of pm_set_power()
+ */
+uint_t pm_divertdebug = 1;
+kmutex_t pm_debug_lock; /* protects pm_divertdebug */
+
+void prdeps(char *);
+#endif
+
+/* Globals */
+
+/*
+ * List of recorded thresholds and dependencies
+ */
+pm_thresh_rec_t *pm_thresh_head;
+krwlock_t pm_thresh_rwlock;
+
+pm_pdr_t *pm_dep_head;
+static int pm_unresolved_deps = 0;
+static int pm_prop_deps = 0;
+
+/*
+ * List of devices that exported no-involuntary-power-cycles property
+ */
+pm_noinvol_t *pm_noinvol_head;
+
+/*
+ * Locks used in noinvol processing
+ */
+krwlock_t pm_noinvol_rwlock;
+kmutex_t pm_remdrv_lock;
+
+int pm_default_idle_threshold = PM_DEFAULT_SYS_IDLENESS;
+int pm_system_idle_threshold;
+/*
+ * By default nexus has 0 threshold, and depends on its children to keep it up
+ */
+int pm_default_nexus_threshold = 0;
+
+/*
+ * Data structures shared with common/io/pm.c
+ */
+kmutex_t pm_clone_lock;
+kcondvar_t pm_clones_cv[PM_MAX_CLONE];
+uint_t pm_poll_cnt[PM_MAX_CLONE]; /* count of events for poll */
+unsigned char pm_interest[PM_MAX_CLONE];
+struct pollhead pm_pollhead;
+
+extern int hz;
+extern char *platform_module_list[];
+
+/*
+ * Wrappers for use in ddi_walk_devs
+ */
+
+static int pm_set_dev_thr_walk(dev_info_t *, void *);
+static int pm_restore_direct_lvl_walk(dev_info_t *, void *);
+static int pm_save_direct_lvl_walk(dev_info_t *, void *);
+static int pm_discard_dep_walk(dev_info_t *, void *);
+#ifdef DEBUG
+static int pm_desc_pwrchk_walk(dev_info_t *, void *);
+#endif
+
+/*
+ * Routines for managing noinvol devices
+ */
+int pm_noinvol_update(int, int, int, char *, dev_info_t *);
+void pm_noinvol_update_node(dev_info_t *,
+ pm_bp_noinvol_t *req);
+
+kmutex_t pm_rsvp_lock;
+kmutex_t pm_compcnt_lock;
+krwlock_t pm_pscc_direct_rwlock;
+krwlock_t pm_pscc_interest_rwlock;
+
+#define PSC_INTEREST 0 /* belongs to interest psc list */
+#define PSC_DIRECT 1 /* belongs to direct psc list */
+
+pscc_t *pm_pscc_interest;
+pscc_t *pm_pscc_direct;
+
+#define PM_MAJOR(dip) ddi_name_to_major(ddi_binding_name(dip))
+#define PM_IS_NEXUS(dip) NEXUS_DRV(devopsp[PM_MAJOR(dip)])
+#define POWERING_ON(old, new) ((old) == 0 && (new) != 0)
+#define POWERING_OFF(old, new) ((old) != 0 && (new) == 0)
+#define PPM(dip) ((dev_info_t *)DEVI(dip)->devi_pm_ppm)
+
+#define PM_INCR_NOTLOWEST(dip) { \
+ mutex_enter(&pm_compcnt_lock); \
+ if (!PM_IS_NEXUS(dip) || \
+ (DEVI(dip)->devi_pm_flags & (PMC_DEV_THRESH|PMC_COMP_THRESH))) {\
+ if (pm_comps_notlowest == 0) \
+ pm_ppm_notify_all_lowest(dip, PM_NOT_ALL_LOWEST);\
+ pm_comps_notlowest++; \
+ PMD(PMD_LEVEL, ("%s: %s@%s(%s#%d) incr notlowest->%d\n",\
+ pmf, PM_DEVICE(dip), pm_comps_notlowest)) \
+ } \
+ mutex_exit(&pm_compcnt_lock); \
+}
+#define PM_DECR_NOTLOWEST(dip) { \
+ mutex_enter(&pm_compcnt_lock); \
+ if (!PM_IS_NEXUS(dip) || \
+ (DEVI(dip)->devi_pm_flags & (PMC_DEV_THRESH|PMC_COMP_THRESH))) {\
+ ASSERT(pm_comps_notlowest); \
+ pm_comps_notlowest--; \
+ PMD(PMD_LEVEL, ("%s: %s@%s(%s#%d) decr notlowest to " \
+ "%d\n", pmf, PM_DEVICE(dip), pm_comps_notlowest))\
+ if (pm_comps_notlowest == 0) \
+ pm_ppm_notify_all_lowest(dip, PM_ALL_LOWEST); \
+ } \
+ mutex_exit(&pm_compcnt_lock); \
+}
+
+/*
+ * console frame-buffer power-management is not enabled when
+ * debugging services are present. to override, set pm_cfb_override
+ * to non-zero.
+ */
+uint_t pm_cfb_comps_off = 0; /* PM_LEVEL_UNKNOWN is considered on */
+kmutex_t pm_cfb_lock;
+int pm_cfb_enabled = 1; /* non-zero allows pm of console frame buffer */
+#ifdef DEBUG
+int pm_cfb_override = 1; /* non-zero allows pm of cfb with debuggers */
+#else
+int pm_cfb_override = 0; /* non-zero allows pm of cfb with debuggers */
+#endif
+
+static dev_info_t *cfb_dip = 0;
+static dev_info_t *cfb_dip_detaching = 0;
+uint_t cfb_inuse = 0;
+static ddi_softintr_t pm_soft_id;
+static clock_t pm_soft_pending;
+int pm_scans_disabled = 0;
+
+/*
+ * A structure to record the fact that one thread has borrowed a lock held
+ * by another thread. The context requires that the lender block on the
+ * completion of the borrower.
+ */
+typedef struct lock_loan {
+ struct lock_loan *pmlk_next;
+ kthread_t *pmlk_borrower;
+ kthread_t *pmlk_lender;
+ dev_info_t *pmlk_dip;
+} lock_loan_t;
+static lock_loan_t lock_loan_head; /* list head is a dummy element */
+
+#ifdef DEBUG
+#define PMD_FUNC(func, name) char *(func) = (name);
+#else
+#define PMD_FUNC(func, name)
+#endif
+
+
+/*
+ * Must be called before first device (including pseudo) attach
+ */
+void
+pm_init_locks(void)
+{
+ mutex_init(&pm_scan_lock, NULL, MUTEX_DRIVER, NULL);
+ mutex_init(&pm_rsvp_lock, NULL, MUTEX_DRIVER, NULL);
+ mutex_init(&pm_compcnt_lock, NULL, MUTEX_DRIVER, NULL);
+ mutex_init(&pm_dep_thread_lock, NULL, MUTEX_DRIVER, NULL);
+ mutex_init(&pm_remdrv_lock, NULL, MUTEX_DRIVER, NULL);
+ mutex_init(&pm_loan_lock, NULL, MUTEX_DRIVER, NULL);
+ rw_init(&pm_thresh_rwlock, NULL, RW_DEFAULT, NULL);
+ rw_init(&pm_noinvol_rwlock, NULL, RW_DEFAULT, NULL);
+ cv_init(&pm_dep_thread_cv, NULL, CV_DEFAULT, NULL);
+}
+
+static boolean_t
+pm_cpr_callb(void *arg, int code)
+{
+ _NOTE(ARGUNUSED(arg))
+ static int auto_save;
+ static int pm_reset_timestamps(dev_info_t *, void *);
+
+ switch (code) {
+ case CB_CODE_CPR_CHKPT:
+ /*
+ * Cancel scan or wait for scan in progress to finish
+ * Other threads may be trying to restart the scan, so we
+ * have to keep at it unil it sticks
+ */
+ mutex_enter(&pm_scan_lock);
+ ASSERT(!pm_scans_disabled);
+ pm_scans_disabled = 1;
+ auto_save = autopm_enabled;
+ autopm_enabled = 0;
+ mutex_exit(&pm_scan_lock);
+ ddi_walk_devs(ddi_root_node(), pm_scan_stop_walk, NULL);
+ break;
+
+ case CB_CODE_CPR_RESUME:
+ ASSERT(!autopm_enabled);
+ ASSERT(pm_scans_disabled);
+ pm_scans_disabled = 0;
+ /*
+ * Call pm_reset_timestamps to reset timestamps of each
+ * device to the time when the system is resumed so that their
+ * idleness can be re-calculated. That's to avoid devices from
+ * being powered down right after resume if the system was in
+ * suspended mode long enough.
+ */
+ ddi_walk_devs(ddi_root_node(), pm_reset_timestamps, NULL);
+
+ autopm_enabled = auto_save;
+ /*
+ * If there is any auto-pm device, get the scanning
+ * going. Otherwise don't bother.
+ */
+ ddi_walk_devs(ddi_root_node(), pm_rescan_walk, NULL);
+ break;
+ }
+ return (B_TRUE);
+}
+
+/*
+ * This callback routine is called when there is a system panic. This function
+ * exists for prototype matching.
+ */
+static boolean_t
+pm_panic_callb(void *arg, int code)
+{
+ _NOTE(ARGUNUSED(arg, code))
+ void pm_cfb_check_and_powerup(void);
+ PMD(PMD_CFB, ("pm_panic_callb\n"))
+ pm_cfb_check_and_powerup();
+ return (B_TRUE);
+}
+
+static boolean_t
+pm_halt_callb(void *arg, int code)
+{
+ _NOTE(ARGUNUSED(arg, code))
+ return (B_TRUE); /* XXX for now */
+}
+
+/*
+ * This needs to be called after the root and platform drivers are loaded
+ * and be single-threaded with respect to driver attach/detach
+ */
+void
+pm_init(void)
+{
+ PMD_FUNC(pmf, "pm_init")
+ char **mod;
+ extern pri_t minclsyspri;
+ static void pm_dep_thread(void);
+
+ pm_comps_notlowest = 0;
+ pm_system_idle_threshold = pm_default_idle_threshold;
+
+ pm_cpr_cb_id = callb_add(pm_cpr_callb, (void *)NULL,
+ CB_CL_CPR_PM, "pm_cpr");
+ pm_panic_cb_id = callb_add(pm_panic_callb, (void *)NULL,
+ CB_CL_PANIC, "pm_panic");
+ pm_halt_cb_id = callb_add(pm_halt_callb, (void *)NULL,
+ CB_CL_HALT, "pm_halt");
+
+ /*
+ * Create a thread to do dependency processing.
+ */
+ (void) thread_create(NULL, 0, (void (*)())pm_dep_thread, NULL, 0, &p0,
+ TS_RUN, minclsyspri);
+
+ /*
+ * loadrootmodules already loaded these ppm drivers, now get them
+ * attached so they can claim the root drivers as they attach
+ */
+ for (mod = platform_module_list; *mod; mod++) {
+ if (i_ddi_attach_hw_nodes(*mod) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "!cannot load platform pm driver %s\n",
+ *mod);
+ } else {
+ PMD(PMD_DHR, ("%s: %s (%s)\n", pmf, *mod,
+ ddi_major_to_name(ddi_name_to_major(*mod))))
+ }
+ }
+}
+
+/*
+ * pm_scan_init - create pm scan data structure. Called (if autopm enabled)
+ * when device becomes power managed or after a failed detach and when autopm
+ * is started via PM_START_PM ioctl, and after a CPR resume to get all the
+ * devices scanning again.
+ */
+void
+pm_scan_init(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "scan_init")
+ pm_scan_t *scanp;
+
+ ASSERT(!PM_ISBC(dip));
+
+ PM_LOCK_DIP(dip);
+ scanp = PM_GET_PM_SCAN(dip);
+ if (!scanp) {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d): create scan data\n",
+ pmf, PM_DEVICE(dip)))
+ scanp = kmem_zalloc(sizeof (pm_scan_t), KM_SLEEP);
+ DEVI(dip)->devi_pm_scan = scanp;
+ } else if (scanp->ps_scan_flags & PM_SCAN_STOP) {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d): "
+ "clear PM_SCAN_STOP flag\n", pmf, PM_DEVICE(dip)))
+ scanp->ps_scan_flags &= ~PM_SCAN_STOP;
+ }
+ PM_UNLOCK_DIP(dip);
+}
+
+/*
+ * pm_scan_fini - remove pm scan data structure when stopping pm on the device
+ */
+void
+pm_scan_fini(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "scan_fini")
+ pm_scan_t *scanp;
+
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ ASSERT(!PM_ISBC(dip));
+ PM_LOCK_DIP(dip);
+ scanp = PM_GET_PM_SCAN(dip);
+ if (!scanp) {
+ PM_UNLOCK_DIP(dip);
+ return;
+ }
+
+ ASSERT(!scanp->ps_scan_id && !(scanp->ps_scan_flags &
+ (PM_SCANNING | PM_SCAN_DISPATCHED | PM_SCAN_AGAIN)));
+
+ kmem_free(scanp, sizeof (pm_scan_t));
+ DEVI(dip)->devi_pm_scan = NULL;
+ PM_UNLOCK_DIP(dip);
+}
+
+/*
+ * Given a pointer to a component struct, return the current power level
+ * (struct contains index unless it is a continuous level).
+ * Located here in hopes of getting both this and dev_is_needed into the
+ * cache together
+ */
+static int
+cur_power(pm_component_t *cp)
+{
+ if (cp->pmc_cur_pwr == PM_LEVEL_UNKNOWN)
+ return (cp->pmc_cur_pwr);
+
+ return (cp->pmc_comp.pmc_lvals[cp->pmc_cur_pwr]);
+}
+
+static char *
+pm_decode_direction(int direction)
+{
+ switch (direction) {
+ case PM_LEVEL_UPONLY:
+ return ("up");
+
+ case PM_LEVEL_EXACT:
+ return ("exact");
+
+ case PM_LEVEL_DOWNONLY:
+ return ("down");
+
+ default:
+ return ("INVALID DIRECTION");
+ }
+ _NOTE(NOTREACHED);
+ ASSERT(0);
+}
+
+char *
+pm_decode_op(pm_bus_power_op_t op)
+{
+ switch (op) {
+ case BUS_POWER_CHILD_PWRCHG:
+ return ("CHILD_PWRCHG");
+ case BUS_POWER_NEXUS_PWRUP:
+ return ("NEXUS_PWRUP");
+ case BUS_POWER_PRE_NOTIFICATION:
+ return ("PRE_NOTIFICATION");
+ case BUS_POWER_POST_NOTIFICATION:
+ return ("POST_NOTIFICATION");
+ case BUS_POWER_HAS_CHANGED:
+ return ("HAS_CHANGED");
+ case BUS_POWER_NOINVOL:
+ return ("NOINVOL");
+ default:
+ return ("UNKNOWN OP");
+ }
+ _NOTE(NOTREACHED);
+ ASSERT(0);
+}
+
+/*
+ * Returns true if level is a possible (valid) power level for component
+ */
+int
+e_pm_valid_power(dev_info_t *dip, int cmpt, int level)
+{
+ PMD_FUNC(pmf, "e_pm_valid_power")
+ pm_component_t *cp = PM_CP(dip, cmpt);
+ int i;
+ int *ip = cp->pmc_comp.pmc_lvals;
+ int limit = cp->pmc_comp.pmc_numlevels;
+
+ if (level < 0)
+ return (0);
+ for (i = 0; i < limit; i++) {
+ if (level == *ip++)
+ return (1);
+ }
+#ifdef DEBUG
+ if (pm_debug & PMD_FAIL) {
+ ip = cp->pmc_comp.pmc_lvals;
+
+ for (i = 0; i < limit; i++)
+ PMD(PMD_FAIL, ("%s: index=%d, level=%d\n",
+ pmf, i, *ip++))
+ }
+#endif
+ return (0);
+}
+
+/*
+ * Returns true if device is pm'd (after calling pm_start if need be)
+ */
+int
+e_pm_valid_info(dev_info_t *dip, pm_info_t **infop)
+{
+ pm_info_t *info;
+ static int pm_start(dev_info_t *dip);
+
+ /*
+ * Check if the device is power managed if not.
+ * To make the common case (device is power managed already)
+ * fast, we check without the lock. If device is not already
+ * power managed, then we take the lock and the long route through
+ * go get it managed. Devices never go unmanaged until they
+ * detach.
+ */
+ info = PM_GET_PM_INFO(dip);
+ if (!info) {
+ if (!DEVI_IS_ATTACHING(dip)) {
+ return (0);
+ }
+ if (pm_start(dip) != DDI_SUCCESS) {
+ return (0);
+ }
+ info = PM_GET_PM_INFO(dip);
+ }
+ ASSERT(info);
+ if (infop != NULL)
+ *infop = info;
+ return (1);
+}
+
+int
+e_pm_valid_comp(dev_info_t *dip, int cmpt, pm_component_t **cpp)
+{
+ if (cmpt >= 0 && cmpt < PM_NUMCMPTS(dip)) {
+ if (cpp != NULL)
+ *cpp = PM_CP(dip, cmpt);
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+/*
+ * Internal guts of ddi_dev_is_needed and pm_raise/lower_power
+ */
+static int
+dev_is_needed(dev_info_t *dip, int cmpt, int level, int direction)
+{
+ PMD_FUNC(pmf, "din")
+ pm_component_t *cp;
+ char *pathbuf;
+ int result;
+
+ ASSERT(direction == PM_LEVEL_UPONLY || direction == PM_LEVEL_DOWNONLY);
+ if (!e_pm_valid_info(dip, NULL) || !e_pm_valid_comp(dip, cmpt, &cp) ||
+ !e_pm_valid_power(dip, cmpt, level))
+ return (DDI_FAILURE);
+
+ PMD(PMD_DIN, ("%s: %s@%s(%s#%d) cmpt=%d, dir=%s, new=%d, cur=%d\n",
+ pmf, PM_DEVICE(dip), cmpt, pm_decode_direction(direction),
+ level, cur_power(cp)))
+
+ if (pm_set_power(dip, cmpt, level, direction,
+ PM_CANBLOCK_BLOCK, 0, &result) != DDI_SUCCESS) {
+ if (direction == PM_LEVEL_UPONLY) {
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+ cmn_err(CE_WARN, "Device %s failed to power up.",
+ pathbuf);
+ kmem_free(pathbuf, MAXPATHLEN);
+ }
+ PMD(PMD_DIN | PMD_FAIL, ("%s: %s@%s(%s#%d) [%d] %s->%d failed, "
+ "errno %d\n", pmf, PM_DEVICE(dip), cmpt,
+ pm_decode_direction(direction), level, result))
+ return (DDI_FAILURE);
+ }
+
+ PMD(PMD_RESCAN | PMD_DIN, ("%s: pm_rescan %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+ pm_rescan(dip);
+ return (DDI_SUCCESS);
+}
+
+/*
+ * We can get multiple pm_rescan() threads, if one of them discovers
+ * that no scan is running at the moment, it kicks it into action.
+ * Otherwise, it tells the current scanning thread to scan again when
+ * it is done by asserting the PM_SCAN_AGAIN flag. The PM_SCANNING and
+ * PM_SCAN_AGAIN flags are used to regulate scan, to make sure only one
+ * thread at a time runs the pm_scan_dev() code.
+ */
+void
+pm_rescan(void *arg)
+{
+ PMD_FUNC(pmf, "rescan")
+ dev_info_t *dip = (dev_info_t *)arg;
+ pm_info_t *info;
+ pm_scan_t *scanp;
+ timeout_id_t scanid;
+
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ PM_LOCK_DIP(dip);
+ info = PM_GET_PM_INFO(dip);
+ scanp = PM_GET_PM_SCAN(dip);
+ if (pm_scans_disabled || !autopm_enabled || !info || !scanp ||
+ (scanp->ps_scan_flags & PM_SCAN_STOP)) {
+ PM_UNLOCK_DIP(dip);
+ return;
+ }
+ if (scanp->ps_scan_flags & PM_SCANNING) {
+ scanp->ps_scan_flags |= PM_SCAN_AGAIN;
+ PM_UNLOCK_DIP(dip);
+ return;
+ } else if (scanp->ps_scan_id) {
+ scanid = scanp->ps_scan_id;
+ scanp->ps_scan_id = 0;
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d): cancel timeout scanid %lx\n",
+ pmf, PM_DEVICE(dip), (ulong_t)scanid))
+ PM_UNLOCK_DIP(dip);
+ (void) untimeout(scanid);
+ PM_LOCK_DIP(dip);
+ }
+
+ /*
+ * Dispatching pm_scan during attach time is risky due to the fact that
+ * attach might soon fail and dip dissolved, and panic may happen while
+ * attempting to stop scan. So schedule a pm_rescan instead.
+ * (Note that if either of the first two terms are true, taskq_dispatch
+ * will not be invoked).
+ *
+ * Multiple pm_scan dispatching is unecessary and costly to keep track
+ * of. The PM_SCAN_DISPATCHED flag is used between pm_rescan and pm_scan
+ * to regulate the dispatching.
+ *
+ * Scan is stopped before the device is detached (in pm_detaching())
+ * but it may get re-started during the post_detach processing if the
+ * driver fails to detach.
+ */
+ if (DEVI_IS_ATTACHING(dip) ||
+ (scanp->ps_scan_flags & PM_SCAN_DISPATCHED) ||
+ !taskq_dispatch(system_taskq, pm_scan, (void *)dip, TQ_NOSLEEP)) {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d): attaching, pm_scan already "
+ "dispatched or dispatching failed\n", pmf, PM_DEVICE(dip)))
+ if (scanp->ps_scan_id) {
+ scanid = scanp->ps_scan_id;
+ scanp->ps_scan_id = 0;
+ PM_UNLOCK_DIP(dip);
+ (void) untimeout(scanid);
+ PM_LOCK_DIP(dip);
+ if (scanp->ps_scan_id) {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d): a competing "
+ "thread scheduled pm_rescan, scanid %lx\n",
+ pmf, PM_DEVICE(dip),
+ (ulong_t)scanp->ps_scan_id))
+ PM_UNLOCK_DIP(dip);
+ return;
+ }
+ }
+ scanp->ps_scan_id = timeout(pm_rescan, (void *)dip,
+ (scanp->ps_idle_down ? pm_id_ticks :
+ (pm_min_scan * hz)));
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d): scheduled next pm_rescan, "
+ "scanid %lx\n", pmf, PM_DEVICE(dip),
+ (ulong_t)scanp->ps_scan_id))
+ } else {
+ PMD(PMD_SCAN, ("%s: dispatched pm_scan for %s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(dip)))
+ scanp->ps_scan_flags |= PM_SCAN_DISPATCHED;
+ }
+ PM_UNLOCK_DIP(dip);
+}
+
+void
+pm_scan(void *arg)
+{
+ PMD_FUNC(pmf, "scan")
+ dev_info_t *dip = (dev_info_t *)arg;
+ pm_scan_t *scanp;
+ time_t nextscan;
+
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+
+ PM_LOCK_DIP(dip);
+ scanp = PM_GET_PM_SCAN(dip);
+ ASSERT(scanp && PM_GET_PM_INFO(dip));
+
+ if (pm_scans_disabled || !autopm_enabled ||
+ (scanp->ps_scan_flags & PM_SCAN_STOP)) {
+ scanp->ps_scan_flags &= ~(PM_SCAN_AGAIN | PM_SCAN_DISPATCHED);
+ PM_UNLOCK_DIP(dip);
+ return;
+ }
+
+ if (scanp->ps_idle_down) {
+ /*
+ * make sure we remember idledown was in affect until
+ * we've completed the scan
+ */
+ PMID_SET_SCANS(scanp->ps_idle_down)
+ PMD(PMD_IDLEDOWN, ("%s: %s@%s(%s#%d): idledown starts "
+ "(pmid %x)\n", pmf, PM_DEVICE(dip), scanp->ps_idle_down))
+ }
+
+ /* possible having two threads running pm_scan() */
+ if (scanp->ps_scan_flags & PM_SCANNING) {
+ scanp->ps_scan_flags |= PM_SCAN_AGAIN;
+ PMD(PMD_SCAN, ("%s: scanning, will scan %s@%s(%s#%d) again\n",
+ pmf, PM_DEVICE(dip)))
+ scanp->ps_scan_flags &= ~PM_SCAN_DISPATCHED;
+ PM_UNLOCK_DIP(dip);
+ return;
+ }
+
+ scanp->ps_scan_flags |= PM_SCANNING;
+ scanp->ps_scan_flags &= ~PM_SCAN_DISPATCHED;
+ do {
+ scanp->ps_scan_flags &= ~PM_SCAN_AGAIN;
+ PM_UNLOCK_DIP(dip);
+ nextscan = pm_scan_dev(dip);
+ PM_LOCK_DIP(dip);
+ } while (scanp->ps_scan_flags & PM_SCAN_AGAIN);
+
+ ASSERT(scanp->ps_scan_flags & PM_SCANNING);
+ scanp->ps_scan_flags &= ~PM_SCANNING;
+
+ if (scanp->ps_idle_down) {
+ scanp->ps_idle_down &= ~PMID_SCANS;
+ PMD(PMD_IDLEDOWN, ("%s: %s@%s(%s#%d): idledown ends "
+ "(pmid %x)\n", pmf, PM_DEVICE(dip), scanp->ps_idle_down))
+ }
+
+ /* schedule for next idle check */
+ if (nextscan != LONG_MAX) {
+ if (nextscan > (LONG_MAX / hz))
+ nextscan = (LONG_MAX - 1) / hz;
+ if (scanp->ps_scan_id) {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d): while scanning "
+ "another rescan scheduled scanid(%lx)\n", pmf,
+ PM_DEVICE(dip), (ulong_t)scanp->ps_scan_id))
+ PM_UNLOCK_DIP(dip);
+ return;
+ } else if (!(scanp->ps_scan_flags & PM_SCAN_STOP)) {
+ scanp->ps_scan_id = timeout(pm_rescan, (void *)dip,
+ (clock_t)(nextscan * hz));
+ PMD(PMD_SCAN, ("%s: nextscan for %s@%s(%s#%d) in "
+ "%lx sec, scanid(%lx) \n", pmf, PM_DEVICE(dip),
+ (ulong_t)nextscan, (ulong_t)scanp->ps_scan_id))
+ }
+ }
+ PM_UNLOCK_DIP(dip);
+}
+
+void
+pm_get_timestamps(dev_info_t *dip, time_t *valuep)
+{
+ int components = PM_NUMCMPTS(dip);
+ int i;
+
+ ASSERT(components > 0);
+ PM_LOCK_BUSY(dip); /* so we get a consistent view */
+ for (i = 0; i < components; i++) {
+ valuep[i] = PM_CP(dip, i)->pmc_timestamp;
+ }
+ PM_UNLOCK_BUSY(dip);
+}
+
+/*
+ * Returns true if device needs to be kept up because it exported the
+ * "no-involuntary-power-cycles" property or we're pretending it did (console
+ * fb case) or it is an ancestor of such a device and has used up the "one
+ * free cycle" allowed when all such leaf nodes have voluntarily powered down
+ * upon detach
+ */
+int
+pm_noinvol(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "noinvol")
+
+ /*
+ * This doesn't change over the life of a driver, so no locking needed
+ */
+ if (PM_IS_CFB(dip)) {
+ PMD(PMD_NOINVOL | PMD_CFB, ("%s: inhibits CFB %s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(dip)))
+ return (1);
+ }
+ /*
+ * Not an issue if no such kids
+ */
+ if (DEVI(dip)->devi_pm_noinvolpm == 0) {
+#ifdef DEBUG
+ if (DEVI(dip)->devi_pm_volpmd != 0) {
+ dev_info_t *pdip = dip;
+ do {
+ PMD(PMD_NOINVOL, ("%s: %s@%s(%s#%d) noinvol %d "
+ "volpmd %d\n", pmf, PM_DEVICE(pdip),
+ DEVI(pdip)->devi_pm_noinvolpm,
+ DEVI(pdip)->devi_pm_volpmd))
+ pdip = ddi_get_parent(pdip);
+ } while (pdip);
+ }
+#endif
+ ASSERT(DEVI(dip)->devi_pm_volpmd == 0);
+ return (0);
+ }
+
+ /*
+ * Since we now maintain the counts correct at every node, we no longer
+ * need to look up the tree. An ancestor cannot use up the free cycle
+ * without the children getting their counts adjusted.
+ */
+
+#ifdef DEBUG
+ if (DEVI(dip)->devi_pm_noinvolpm != DEVI(dip)->devi_pm_volpmd)
+ PMD(PMD_NOINVOL, ("%s: (%d != %d) inhibits %s@%s(%s#%d)\n", pmf,
+ DEVI(dip)->devi_pm_noinvolpm, DEVI(dip)->devi_pm_volpmd,
+ PM_DEVICE(dip)))
+#endif
+ return (DEVI(dip)->devi_pm_noinvolpm != DEVI(dip)->devi_pm_volpmd);
+}
+
+/*
+ * This function performs the actual scanning of the device.
+ * It attempts to power off the indicated device's components if they have
+ * been idle and other restrictions are met.
+ * pm_scan_dev calculates and returns when the next scan should happen for
+ * this device.
+ */
+time_t
+pm_scan_dev(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "scan_dev")
+ pm_scan_t *scanp;
+ time_t *timestamp, idletime, now, thresh;
+ time_t timeleft = 0;
+ int i, nxtpwr, curpwr, pwrndx, unused;
+ size_t size;
+ pm_component_t *cp;
+ dev_info_t *pdip = ddi_get_parent(dip);
+ int circ;
+ static int cur_threshold(dev_info_t *, int);
+ static int pm_next_lower_power(pm_component_t *, int);
+
+ /*
+ * skip attaching device
+ */
+ if (DEVI_IS_ATTACHING(dip)) {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d) is attaching, timeleft(%lx)\n",
+ pmf, PM_DEVICE(dip), pm_min_scan))
+ return (pm_min_scan);
+ }
+
+ PM_LOCK_DIP(dip);
+ scanp = PM_GET_PM_SCAN(dip);
+ ASSERT(scanp && PM_GET_PM_INFO(dip));
+
+ PMD(PMD_SCAN, ("%s: [BEGIN %s@%s(%s#%d)]\n", pmf, PM_DEVICE(dip)))
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d): kuc is %d\n", pmf, PM_DEVICE(dip),
+ PM_KUC(dip)))
+
+ /* no scan under the following conditions */
+ if (pm_scans_disabled || !autopm_enabled ||
+ (scanp->ps_scan_flags & PM_SCAN_STOP) ||
+ (PM_KUC(dip) != 0) ||
+ PM_ISDIRECT(dip) || pm_noinvol(dip)) {
+ PM_UNLOCK_DIP(dip);
+ PMD(PMD_SCAN, ("%s: [END, %s@%s(%s#%d)] no scan, "
+ "scan_disabled(%d), apm_enabled(%d), kuc(%d), "
+ "%s directpm, %s pm_noinvol\n", pmf, PM_DEVICE(dip),
+ pm_scans_disabled, autopm_enabled, PM_KUC(dip),
+ PM_ISDIRECT(dip) ? "is" : "is not",
+ pm_noinvol(dip) ? "is" : "is not"))
+ return (LONG_MAX);
+ }
+ PM_UNLOCK_DIP(dip);
+
+ if (!ndi_devi_tryenter(pdip, &circ)) {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d) can't hold pdip",
+ pmf, PM_DEVICE(pdip)))
+ return ((time_t)1);
+ }
+ now = gethrestime_sec();
+ size = PM_NUMCMPTS(dip) * sizeof (time_t);
+ timestamp = kmem_alloc(size, KM_SLEEP);
+ pm_get_timestamps(dip, timestamp);
+
+ /*
+ * Since we removed support for backwards compatible devices,
+ * (see big comment at top of file)
+ * it is no longer required to deal with component 0 last.
+ */
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ /*
+ * If already off (an optimization, perhaps)
+ */
+ cp = PM_CP(dip, i);
+ pwrndx = cp->pmc_cur_pwr;
+ curpwr = (pwrndx == PM_LEVEL_UNKNOWN) ?
+ PM_LEVEL_UNKNOWN :
+ cp->pmc_comp.pmc_lvals[pwrndx];
+
+ if (pwrndx == 0) {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d) comp %d off or "
+ "lowest\n", pmf, PM_DEVICE(dip), i))
+ /* skip device if off or at its lowest */
+ continue;
+ }
+
+ thresh = cur_threshold(dip, i); /* comp i threshold */
+ if ((timestamp[i] == 0) || (cp->pmc_busycount > 0)) {
+ /* were busy or newly became busy by another thread */
+ if (timeleft == 0)
+ timeleft = max(thresh, pm_min_scan);
+ else
+ timeleft = min(
+ timeleft, max(thresh, pm_min_scan));
+ continue;
+ }
+
+ idletime = now - timestamp[i]; /* idle time */
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d) comp %d idle time %lx\n",
+ pmf, PM_DEVICE(dip), i, idletime))
+ if (idletime >= thresh || PM_IS_PID(dip)) {
+ nxtpwr = pm_next_lower_power(cp, pwrndx);
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d) comp %d, %d->%d\n",
+ pmf, PM_DEVICE(dip), i, curpwr, nxtpwr))
+ if (pm_set_power(dip, i, nxtpwr, PM_LEVEL_DOWNONLY,
+ PM_CANBLOCK_FAIL, 1, &unused) != DDI_SUCCESS &&
+ PM_CURPOWER(dip, i) != nxtpwr) {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d) comp %d, "
+ "%d->%d Failed\n", pmf, PM_DEVICE(dip),
+ i, curpwr, nxtpwr))
+ timeleft = pm_min_scan;
+ continue;
+ } else {
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d) comp %d, "
+ "%d->%d, GOOD curpwr %d\n", pmf,
+ PM_DEVICE(dip), i, curpwr, nxtpwr,
+ cur_power(cp)))
+
+ if (nxtpwr == 0) /* component went off */
+ continue;
+
+ /*
+ * scan to next lower level
+ */
+ if (timeleft == 0)
+ timeleft = max(
+ 1, cur_threshold(dip, i));
+ else
+ timeleft = min(timeleft,
+ max(1, cur_threshold(dip, i)));
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d) comp %d, "
+ "timeleft(%lx)\n", pmf, PM_DEVICE(dip),
+ i, timeleft))
+ }
+ } else { /* comp not idle long enough */
+ if (timeleft == 0)
+ timeleft = thresh - idletime;
+ else
+ timeleft = min(timeleft, (thresh - idletime));
+ PMD(PMD_SCAN, ("%s: %s@%s(%s#%d) comp %d, timeleft="
+ "%lx\n", pmf, PM_DEVICE(dip), i, timeleft))
+ }
+ }
+ ndi_devi_exit(pdip, circ);
+ kmem_free(timestamp, size);
+ PMD(PMD_SCAN, ("%s: [END %s@%s(%s#%d)] timeleft(%lx)\n", pmf,
+ PM_DEVICE(dip), timeleft))
+
+ /*
+ * if components are already at lowest level, timeleft is left 0
+ */
+ return ((timeleft == 0) ? LONG_MAX : timeleft);
+}
+
+/*
+ * pm_scan_stop - cancel scheduled pm_rescan,
+ * wait for termination of dispatched pm_scan thread
+ * and active pm_scan_dev thread.
+ */
+void
+pm_scan_stop(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "scan_stop")
+ pm_scan_t *scanp;
+ timeout_id_t scanid;
+
+ PMD(PMD_SCAN, ("%s: [BEGIN %s@%s(%s#%d)]\n", pmf, PM_DEVICE(dip)))
+ PM_LOCK_DIP(dip);
+ scanp = PM_GET_PM_SCAN(dip);
+ if (!scanp) {
+ PMD(PMD_SCAN, ("%s: [END %s@%s(%s#%d)] scan not initialized\n",
+ pmf, PM_DEVICE(dip)))
+ PM_UNLOCK_DIP(dip);
+ return;
+ }
+ scanp->ps_scan_flags |= PM_SCAN_STOP;
+
+ /* cancel scheduled scan taskq */
+ while (scanp->ps_scan_id) {
+ scanid = scanp->ps_scan_id;
+ scanp->ps_scan_id = 0;
+ PM_UNLOCK_DIP(dip);
+ (void) untimeout(scanid);
+ PM_LOCK_DIP(dip);
+ }
+
+ while (scanp->ps_scan_flags & (PM_SCANNING | PM_SCAN_DISPATCHED)) {
+ PM_UNLOCK_DIP(dip);
+ delay(1);
+ PM_LOCK_DIP(dip);
+ }
+ PM_UNLOCK_DIP(dip);
+ PMD(PMD_SCAN, ("%s: [END %s@%s(%s#%d)]\n", pmf, PM_DEVICE(dip)))
+}
+
+int
+pm_scan_stop_walk(dev_info_t *dip, void *arg)
+{
+ _NOTE(ARGUNUSED(arg))
+
+ if (!PM_GET_PM_SCAN(dip))
+ return (DDI_WALK_CONTINUE);
+ ASSERT(!PM_ISBC(dip));
+ pm_scan_stop(dip);
+ return (DDI_WALK_CONTINUE);
+}
+
+/*
+ * Converts a power level value to its index
+ */
+static int
+power_val_to_index(pm_component_t *cp, int val)
+{
+ int limit, i, *ip;
+
+ ASSERT(val != PM_LEVEL_UPONLY && val != PM_LEVEL_DOWNONLY &&
+ val != PM_LEVEL_EXACT);
+ /* convert power value into index (i) */
+ limit = cp->pmc_comp.pmc_numlevels;
+ ip = cp->pmc_comp.pmc_lvals;
+ for (i = 0; i < limit; i++)
+ if (val == *ip++)
+ return (i);
+ return (-1);
+}
+
+/*
+ * Converts a numeric power level to a printable string
+ */
+static char *
+power_val_to_string(pm_component_t *cp, int val)
+{
+ int index;
+
+ if (val == PM_LEVEL_UPONLY)
+ return ("<UPONLY>");
+
+ if (val == PM_LEVEL_UNKNOWN ||
+ (index = power_val_to_index(cp, val)) == -1)
+ return ("<LEVEL_UNKNOWN>");
+
+ return (cp->pmc_comp.pmc_lnames[index]);
+}
+
+/*
+ * Return true if this node has been claimed by a ppm.
+ */
+static int
+pm_ppm_claimed(dev_info_t *dip)
+{
+ return (PPM(dip) != NULL);
+}
+
+/*
+ * A node which was voluntarily power managed has just used up its "free cycle"
+ * and need is volpmd field cleared, and the same done to all its descendents
+ */
+static void
+pm_clear_volpm_dip(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "clear_volpm_dip")
+
+ if (dip == NULL)
+ return;
+ PMD(PMD_NOINVOL, ("%s: clear volpm from %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+ DEVI(dip)->devi_pm_volpmd = 0;
+ for (dip = ddi_get_child(dip); dip; dip = ddi_get_next_sibling(dip)) {
+ pm_clear_volpm_dip(dip);
+ }
+}
+
+/*
+ * A node which was voluntarily power managed has used up the "free cycles"
+ * for the subtree that it is the root of. Scan through the list of detached
+ * nodes and adjust the counts of any that are descendents of the node.
+ */
+static void
+pm_clear_volpm_list(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "clear_volpm_list")
+ char *pathbuf;
+ size_t len;
+ pm_noinvol_t *ip;
+
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+ len = strlen(pathbuf);
+ PMD(PMD_NOINVOL, ("%s: clear volpm list %s\n", pmf, pathbuf))
+ rw_enter(&pm_noinvol_rwlock, RW_WRITER);
+ for (ip = pm_noinvol_head; ip; ip = ip->ni_next) {
+ PMD(PMD_NOINVOL, ("%s: clear volpm: ni_path %s\n", pmf,
+ ip->ni_path))
+ if (strncmp(pathbuf, ip->ni_path, len) == 0 &&
+ ip->ni_path[len] == '/') {
+ PMD(PMD_NOINVOL, ("%s: clear volpm: %s\n", pmf,
+ ip->ni_path))
+ ip->ni_volpmd = 0;
+ ip->ni_wasvolpmd = 0;
+ }
+ }
+ kmem_free(pathbuf, MAXPATHLEN);
+ rw_exit(&pm_noinvol_rwlock);
+}
+
+/*
+ * Powers a device, suspending or resuming the driver if it is a backward
+ * compatible device, calling into ppm to change power level.
+ * Called with the component's power lock held.
+ */
+static int
+power_dev(dev_info_t *dip, int comp, int level, int old_level,
+ pm_canblock_t canblock, pm_ppm_devlist_t **devlist)
+{
+ PMD_FUNC(pmf, "power_dev")
+ power_req_t power_req;
+ int power_op_ret; /* DDI_SUCCESS or DDI_FAILURE */
+ int resume_needed = 0;
+ int suspended = 0;
+ int result;
+ struct pm_component *cp = PM_CP(dip, comp);
+ int bc = PM_ISBC(dip);
+ int pm_all_components_off(dev_info_t *);
+ int clearvolpmd = 0;
+ char pathbuf[MAXNAMELEN];
+#ifdef DEBUG
+ char *ppmname, *ppmaddr;
+#endif
+ /*
+ * If this is comp 0 of a backwards compat device and we are
+ * going to take the power away, we need to detach it with
+ * DDI_PM_SUSPEND command.
+ */
+ if (bc && comp == 0 && POWERING_OFF(old_level, level)) {
+ if (devi_detach(dip, DDI_PM_SUSPEND) != DDI_SUCCESS) {
+ /* We could not suspend before turning cmpt zero off */
+ PMD(PMD_ERROR, ("%s: could not suspend %s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(dip)))
+ return (DDI_FAILURE);
+ } else {
+ DEVI(dip)->devi_pm_flags |= PMC_SUSPENDED;
+ suspended++;
+ }
+ }
+ power_req.request_type = PMR_PPM_SET_POWER;
+ power_req.req.ppm_set_power_req.who = dip;
+ power_req.req.ppm_set_power_req.cmpt = comp;
+ power_req.req.ppm_set_power_req.old_level = old_level;
+ power_req.req.ppm_set_power_req.new_level = level;
+ power_req.req.ppm_set_power_req.canblock = canblock;
+ power_req.req.ppm_set_power_req.cookie = NULL;
+#ifdef DEBUG
+ if (pm_ppm_claimed(dip)) {
+ ppmname = PM_NAME(PPM(dip));
+ ppmaddr = PM_ADDR(PPM(dip));
+
+ } else {
+ ppmname = "noppm";
+ ppmaddr = "0";
+ }
+ PMD(PMD_PPM, ("%s: %s@%s(%s#%d):%s[%d] %s (%d) -> %s (%d) via %s@%s\n",
+ pmf, PM_DEVICE(dip), cp->pmc_comp.pmc_name, comp,
+ power_val_to_string(cp, old_level), old_level,
+ power_val_to_string(cp, level), level, ppmname, ppmaddr))
+#endif
+ /*
+ * If non-bc noinvolpm device is turning first comp on, or noinvolpm
+ * bc device comp 0 is powering on, then we count it as a power cycle
+ * against its voluntary count.
+ */
+ if (DEVI(dip)->devi_pm_volpmd &&
+ (!bc && pm_all_components_off(dip) && level != 0) ||
+ (bc && comp == 0 && POWERING_ON(old_level, level)))
+ clearvolpmd = 1;
+ if ((power_op_ret = pm_ctlops(PPM(dip), dip, DDI_CTLOPS_POWER,
+ &power_req, &result)) == DDI_SUCCESS) {
+ /*
+ * Now do involuntary pm accounting; If we've just cycled power
+ * on a voluntarily pm'd node, and by inference on its entire
+ * subtree, we need to set the subtree (including those nodes
+ * already detached) volpmd counts to 0, and subtract out the
+ * value of the current node's volpmd count from the ancestors
+ */
+ if (clearvolpmd) {
+ int volpmd = DEVI(dip)->devi_pm_volpmd;
+ pm_clear_volpm_dip(dip);
+ pm_clear_volpm_list(dip);
+ if (volpmd) {
+ (void) ddi_pathname(dip, pathbuf);
+ (void) pm_noinvol_update(PM_BP_NOINVOL_POWER,
+ volpmd, 0, pathbuf, dip);
+ }
+ }
+ } else {
+ PMD(PMD_FAIL, ("%s: can't set comp %d (%s) of %s@%s(%s#%d) "
+ "to level %d (%s)\n", pmf, comp, cp->pmc_comp.pmc_name,
+ PM_DEVICE(dip), level, power_val_to_string(cp, level)))
+ }
+ /*
+ * If some other devices were also powered up (e.g. other cpus in
+ * the same domain) return a pointer to that list
+ */
+ if (devlist) {
+ *devlist = (pm_ppm_devlist_t *)
+ power_req.req.ppm_set_power_req.cookie;
+ }
+ /*
+ * We will have to resume the device if the device is backwards compat
+ * device and either of the following is true:
+ * -This is comp 0 and we have successfully powered it up
+ * -This is comp 0 and we have failed to power it down. Resume is
+ * needed because we have suspended it above
+ */
+
+ if (bc && comp == 0) {
+ ASSERT(PM_ISDIRECT(dip) || DEVI_IS_DETACHING(dip));
+ if (power_op_ret == DDI_SUCCESS) {
+ if (POWERING_ON(old_level, level)) {
+ /*
+ * It must be either suspended or resumed
+ * via pm_power_has_changed path
+ */
+ ASSERT((DEVI(dip)->devi_pm_flags &
+ PMC_SUSPENDED) ||
+ (PM_CP(dip, comp)->pmc_flags &
+ PM_PHC_WHILE_SET_POWER));
+
+ resume_needed = suspended;
+ }
+ } else {
+ if (POWERING_OFF(old_level, level)) {
+ /*
+ * It must be either suspended or resumed
+ * via pm_power_has_changed path
+ */
+ ASSERT((DEVI(dip)->devi_pm_flags &
+ PMC_SUSPENDED) ||
+ (PM_CP(dip, comp)->pmc_flags &
+ PM_PHC_WHILE_SET_POWER));
+
+ resume_needed = suspended;
+ }
+ }
+ }
+ if (resume_needed) {
+ ASSERT(DEVI(dip)->devi_pm_flags & PMC_SUSPENDED);
+ /* ppm is not interested in DDI_PM_RESUME */
+ if ((power_op_ret = devi_attach(dip, DDI_PM_RESUME)) ==
+ DDI_SUCCESS) {
+ DEVI(dip)->devi_pm_flags &= ~PMC_SUSPENDED;
+ } else
+ cmn_err(CE_WARN, "!pm: Can't resume %s@%s(%s#%d)",
+ PM_DEVICE(dip));
+ }
+ return (power_op_ret);
+}
+
+/*
+ * Return true if we are the owner or a borrower of the devi lock. See
+ * pm_lock_power_single() about borrowing the lock.
+ */
+static int
+pm_devi_lock_held(dev_info_t *dip)
+{
+ lock_loan_t *cur;
+
+ if (DEVI_BUSY_OWNED(dip))
+ return (1);
+
+ /* return false if no locks borrowed */
+ if (lock_loan_head.pmlk_next == NULL)
+ return (0);
+
+ mutex_enter(&pm_loan_lock);
+ /* see if our thread is registered as a lock borrower. */
+ for (cur = lock_loan_head.pmlk_next; cur; cur = cur->pmlk_next)
+ if (cur->pmlk_borrower == curthread)
+ break;
+ mutex_exit(&pm_loan_lock);
+
+ return (cur != NULL && cur->pmlk_lender == DEVI(dip)->devi_busy_thread);
+}
+
+/*
+ * pm_set_power: adjusts power level of device. Assumes device is power
+ * manageable & component exists.
+ *
+ * Cases which require us to bring up devices we keep up ("wekeepups") for
+ * backwards compatible devices:
+ * component 0 is off and we're bringing it up from 0
+ * bring up wekeepup first
+ * and recursively when component 0 is off and we bring some other
+ * component up from 0
+ * For devices which are not backward compatible, our dependency notion is much
+ * simpler. Unless all components are off, then wekeeps must be on.
+ * We don't treat component 0 differently.
+ * Canblock tells how to deal with a direct pm'd device.
+ * Scan arg tells us if we were called from scan, in which case we don't need
+ * to go back to the root node and walk down to change power.
+ */
+int
+pm_set_power(dev_info_t *dip, int comp, int level, int direction,
+ pm_canblock_t canblock, int scan, int *retp)
+{
+ PMD_FUNC(pmf, "set_power")
+ char *pathbuf;
+ pm_bp_child_pwrchg_t bpc;
+ pm_sp_misc_t pspm;
+ int ret = DDI_SUCCESS;
+ int unused = DDI_SUCCESS;
+ dev_info_t *pdip = ddi_get_parent(dip);
+
+#ifdef DEBUG
+ int diverted = 0;
+
+ /*
+ * This prevents operations on the console from calling prom_printf and
+ * either deadlocking or bringing up the console because of debug
+ * output
+ */
+ if (dip == cfb_dip) {
+ diverted++;
+ mutex_enter(&pm_debug_lock);
+ pm_divertdebug++;
+ mutex_exit(&pm_debug_lock);
+ }
+#endif
+ ASSERT(direction == PM_LEVEL_UPONLY || direction == PM_LEVEL_DOWNONLY ||
+ direction == PM_LEVEL_EXACT);
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d), comp=%d, dir=%s, new=%d\n",
+ pmf, PM_DEVICE(dip), comp, pm_decode_direction(direction), level))
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+ bpc.bpc_dip = dip;
+ bpc.bpc_path = pathbuf;
+ bpc.bpc_comp = comp;
+ bpc.bpc_olevel = PM_CURPOWER(dip, comp);
+ bpc.bpc_nlevel = level;
+ pspm.pspm_direction = direction;
+ pspm.pspm_errnop = retp;
+ pspm.pspm_canblock = canblock;
+ pspm.pspm_scan = scan;
+ bpc.bpc_private = &pspm;
+
+ /*
+ * If a config operation is being done (we've locked the parent) or
+ * we already hold the power lock (we've locked the node)
+ * then we can operate directly on the node because we have already
+ * brought up all the ancestors, otherwise, we have to go back to the
+ * top of the tree.
+ */
+ if (pm_devi_lock_held(pdip) || pm_devi_lock_held(dip))
+ ret = pm_busop_set_power(dip, NULL, BUS_POWER_CHILD_PWRCHG,
+ (void *)&bpc, (void *)&unused);
+ else
+ ret = pm_busop_bus_power(ddi_root_node(), NULL,
+ BUS_POWER_CHILD_PWRCHG, (void *)&bpc, (void *)&unused);
+#ifdef DEBUG
+ if (ret != DDI_SUCCESS || *retp != DDI_SUCCESS) {
+ PMD(PMD_ERROR, ("%s: %s@%s(%s#%d) can't change power, ret=%d, "
+ "errno=%d\n", pmf, PM_DEVICE(dip), ret, *retp))
+ }
+ if (diverted) {
+ mutex_enter(&pm_debug_lock);
+ pm_divertdebug--;
+ mutex_exit(&pm_debug_lock);
+ }
+#endif
+ kmem_free(pathbuf, MAXPATHLEN);
+ return (ret);
+}
+
+
+static dev_info_t *
+find_dip(dev_info_t *dip, char *dev_name, int holddip)
+{
+ PMD_FUNC(pmf, "find_dip")
+ dev_info_t *cdip;
+ char *child_dev, *addr;
+ char *device; /* writeable copy of path */
+ int dev_len = strlen(dev_name)+1;
+ int circ;
+
+ device = kmem_zalloc(dev_len, KM_SLEEP);
+ (void) strcpy(device, dev_name);
+ addr = strchr(device, '@');
+ child_dev = strchr(device, '/');
+ if ((addr != NULL) && (child_dev == NULL || addr < child_dev)) {
+ /*
+ * We have device = "name@addr..." form
+ */
+ *addr++ = '\0'; /* for strcmp (and skip '@') */
+ if (child_dev != NULL)
+ *child_dev++ = '\0'; /* for strcmp (and skip '/') */
+ } else {
+ /*
+ * We have device = "name/..." or "name"
+ */
+ addr = "";
+ if (child_dev != NULL)
+ *child_dev++ = '\0'; /* for strcmp (and skip '/') */
+ }
+ for (; dip != NULL; dip = ddi_get_next_sibling(dip)) {
+ if (strcmp(ddi_node_name(dip), device) == 0) {
+ /* If the driver isn't loaded, we prune the search */
+ if (i_ddi_node_state(dip) < DS_READY) {
+ continue;
+ }
+ if (strcmp(ddi_get_name_addr(dip), addr) == 0) {
+ PMD(PMD_NAMETODIP, ("%s: matched %s@%s"
+ "(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ if (child_dev != NULL) {
+ PMD(PMD_NAMETODIP, ("%s: %s@%s(%s#%d): "
+ "held, call find_dip %s\n", pmf,
+ PM_DEVICE(dip), child_dev))
+ ndi_devi_enter(dip, &circ);
+ cdip = dip;
+ dip = find_dip(ddi_get_child(dip),
+ child_dev, holddip);
+ ndi_devi_exit(cdip, circ);
+ PMD(PMD_NAMETODIP, ("%s: %s@%s(%s#%d): "
+ "release, find_dip rets %s\n", pmf,
+ PM_DEVICE(cdip), child_dev))
+ } else {
+ if (holddip) {
+ e_ddi_hold_devi(dip);
+ PMD(PMD_DHR | PMD_NAMETODIP,
+ ("%s: held %s@%s(%s#%d), "
+ "refcnt=%d\n", pmf,
+ PM_DEVICE(dip),
+ e_ddi_devi_holdcnt(dip)))
+ }
+ }
+ kmem_free(device, dev_len);
+ return (dip);
+ }
+ }
+ }
+ kmem_free(device, dev_len);
+ return (dip);
+}
+
+/*
+ * If holddip is set, then if a dip is found we return with the node held
+ */
+dev_info_t *
+pm_name_to_dip(char *pathname, int holddip)
+{
+ PMD_FUNC(pmf, "name_to_dip")
+ dev_info_t *dip = NULL;
+ char dev_name[MAXNAMELEN];
+ dev_info_t *first_child;
+ int circular;
+
+ if (!pathname)
+ return (NULL);
+
+ (void) strncpy(dev_name, pathname, MAXNAMELEN);
+
+ PMD(PMD_NAMETODIP, ("%s: devname: %s\n", pmf, dev_name))
+
+ /*
+ * First we attempt to match the node in the tree. If we succeed
+ * we hold the driver and look up the dip again.
+ * No need to hold the root as that node is always held.
+ */
+ if (dev_name[0] == '/') {
+ ndi_devi_enter(ddi_root_node(), &circular);
+ first_child = ddi_get_child(ddi_root_node());
+ dip = find_dip(first_child, dev_name + 1, holddip);
+ ndi_devi_exit(ddi_root_node(), circular);
+
+ } else {
+ PMD(PMD_NAMETODIP, ("%s: physpath with unrooted "
+ "search\n", pmf))
+ return (NULL);
+ }
+
+ ASSERT(!dip ||
+ (ddi_name_to_major(ddi_binding_name(dip)) != (major_t)-1));
+
+ return (dip);
+}
+
+/*
+ * Search for a dependency and mark it unsatisfied
+ */
+static void
+pm_unsatisfy(char *keeper, char *kept)
+{
+ PMD_FUNC(pmf, "unsatisfy")
+ pm_pdr_t *dp;
+
+ PMD(PMD_KEEPS, ("%s: keeper=%s, kept=%s\n", pmf, keeper, kept))
+ for (dp = pm_dep_head; dp; dp = dp->pdr_next) {
+ if (!dp->pdr_isprop) {
+ if (strcmp(dp->pdr_keeper, keeper) == 0 &&
+ (dp->pdr_kept_count > 0) &&
+ strcmp(dp->pdr_kept_paths[0], kept) == 0) {
+ if (dp->pdr_satisfied) {
+ dp->pdr_satisfied = 0;
+ pm_unresolved_deps++;
+ PMD(PMD_KEEPS, ("%s: clear satisfied, "
+ "pm_unresolved_deps now %d\n", pmf,
+ pm_unresolved_deps))
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Device dip is being un power managed, it keeps up count other devices.
+ * We need to release any hold we have on the kept devices, and also
+ * mark the dependency no longer satisfied.
+ */
+static void
+pm_unkeeps(int count, char *keeper, char **keptpaths, int pwr)
+{
+ PMD_FUNC(pmf, "unkeeps")
+ int i, j;
+ dev_info_t *kept;
+ dev_info_t *dip;
+ struct pm_component *cp;
+ int keeper_on = 0, circ;
+
+ PMD(PMD_KEEPS, ("%s: count=%d, keeper=%s, keptpaths=%p\n", pmf, count,
+ keeper, (void *)keptpaths))
+ /*
+ * Try to grab keeper. Keeper may have gone away by now,
+ * in this case, used the passed in value pwr
+ */
+ dip = pm_name_to_dip(keeper, 1);
+ for (i = 0; i < count; i++) {
+ /* Release power hold */
+ kept = pm_name_to_dip(keptpaths[i], 1);
+ if (kept) {
+ PMD(PMD_KEEPS, ("%s: %s@%s(%s#%d)[%d]\n", pmf,
+ PM_DEVICE(kept), i))
+ /*
+ * We need to check if we skipped a bringup here
+ * because we could have failed the bringup
+ * (ie DIRECT PM device) and have
+ * not increment the count.
+ */
+ if ((dip != NULL) && (PM_GET_PM_INFO(dip) != NULL)) {
+ keeper_on = 0;
+ PM_LOCK_POWER(dip, &circ);
+ for (j = 0; j < PM_NUMCMPTS(dip); j++) {
+ cp = &DEVI(dip)->devi_pm_components[j];
+ if (cur_power(cp)) {
+ keeper_on++;
+ break;
+ }
+ }
+ if (keeper_on && (PM_SKBU(kept) == 0)) {
+ pm_rele_power(kept);
+ DEVI(kept)->devi_pm_flags
+ &= ~PMC_SKIP_BRINGUP;
+ }
+ PM_UNLOCK_POWER(dip, circ);
+ } else if (pwr) {
+ if (PM_SKBU(kept) == 0) {
+ pm_rele_power(kept);
+ DEVI(kept)->devi_pm_flags
+ &= ~PMC_SKIP_BRINGUP;
+ }
+ }
+ ddi_release_devi(kept);
+ }
+ /*
+ * mark this dependency not satisfied
+ */
+ pm_unsatisfy(keeper, keptpaths[i]);
+ }
+ if (dip)
+ ddi_release_devi(dip);
+}
+
+/*
+ * Device kept is being un power managed, it is kept up by keeper.
+ * We need to mark the dependency no longer satisfied.
+ */
+static void
+pm_unkepts(char *kept, char *keeper)
+{
+ PMD_FUNC(pmf, "unkepts")
+ PMD(PMD_KEEPS, ("%s: kept=%s, keeper=%s\n", pmf, kept, keeper))
+ ASSERT(keeper != NULL);
+ /*
+ * mark this dependency not satisfied
+ */
+ pm_unsatisfy(keeper, kept);
+}
+
+/*
+ * Removes dependency information and hold on the kepts, if the path is a
+ * path of a keeper.
+ */
+static void
+pm_free_keeper(char *path, int pwr)
+{
+ pm_pdr_t *dp;
+ int i;
+ size_t length;
+
+ for (dp = pm_dep_head; dp; dp = dp->pdr_next) {
+ if (strcmp(dp->pdr_keeper, path) != 0)
+ continue;
+ /*
+ * Remove all our kept holds and the dependency records,
+ * then free up the kept lists.
+ */
+ pm_unkeeps(dp->pdr_kept_count, path, dp->pdr_kept_paths, pwr);
+ if (dp->pdr_kept_count) {
+ for (i = 0; i < dp->pdr_kept_count; i++) {
+ length = strlen(dp->pdr_kept_paths[i]);
+ kmem_free(dp->pdr_kept_paths[i], length + 1);
+ }
+ kmem_free(dp->pdr_kept_paths,
+ dp->pdr_kept_count * sizeof (char **));
+ dp->pdr_kept_paths = NULL;
+ dp->pdr_kept_count = 0;
+ }
+ }
+}
+
+/*
+ * Removes the device represented by path from the list of kepts, if the
+ * path is a path of a kept
+ */
+static void
+pm_free_kept(char *path)
+{
+ pm_pdr_t *dp;
+ int i;
+ int j, count;
+ size_t length;
+ char **paths;
+
+ for (dp = pm_dep_head; dp; dp = dp->pdr_next) {
+ if (dp->pdr_kept_count == 0)
+ continue;
+ count = dp->pdr_kept_count;
+ /* Remove this device from the kept path lists */
+ for (i = 0; i < count; i++) {
+ if (strcmp(dp->pdr_kept_paths[i], path) == 0) {
+ pm_unkepts(path, dp->pdr_keeper);
+ length = strlen(dp->pdr_kept_paths[i]) + 1;
+ kmem_free(dp->pdr_kept_paths[i], length);
+ dp->pdr_kept_paths[i] = NULL;
+ dp->pdr_kept_count--;
+ }
+ }
+ /* Compact the kept paths array */
+ if (dp->pdr_kept_count) {
+ length = dp->pdr_kept_count * sizeof (char **);
+ paths = kmem_zalloc(length, KM_SLEEP);
+ j = 0;
+ for (i = 0; i < count; i++) {
+ if (dp->pdr_kept_paths[i] != NULL) {
+ paths[j] = dp->pdr_kept_paths[i];
+ j++;
+ }
+ }
+ ASSERT(j == dp->pdr_kept_count);
+ }
+ /* Now free the old array and point to the new one */
+ kmem_free(dp->pdr_kept_paths, count * sizeof (char **));
+ if (dp->pdr_kept_count)
+ dp->pdr_kept_paths = paths;
+ else
+ dp->pdr_kept_paths = NULL;
+ }
+}
+
+/*
+ * Free the dependency information for a device.
+ */
+void
+pm_free_keeps(char *path, int pwr)
+{
+ PMD_FUNC(pmf, "free_keeps")
+
+#ifdef DEBUG
+ int doprdeps = 0;
+ void prdeps(char *);
+
+ PMD(PMD_KEEPS, ("%s: %s\n", pmf, path))
+ if (pm_debug & PMD_KEEPS) {
+ doprdeps = 1;
+ prdeps("pm_free_keeps before");
+ }
+#endif
+ /*
+ * First assume we are a keeper and remove all our kepts.
+ */
+ pm_free_keeper(path, pwr);
+ /*
+ * Now assume we a kept device, and remove all our records.
+ */
+ pm_free_kept(path);
+#ifdef DEBUG
+ if (doprdeps) {
+ prdeps("pm_free_keeps after");
+ }
+#endif
+}
+
+static int
+pm_is_kept(char *path)
+{
+ pm_pdr_t *dp;
+ int i;
+
+ for (dp = pm_dep_head; dp; dp = dp->pdr_next) {
+ if (dp->pdr_kept_count == 0)
+ continue;
+ for (i = 0; i < dp->pdr_kept_count; i++) {
+ if (strcmp(dp->pdr_kept_paths[i], path) == 0)
+ return (1);
+ }
+ }
+ return (0);
+}
+
+static void
+e_pm_hold_rele_power(dev_info_t *dip, int cnt)
+{
+ PMD_FUNC(pmf, "hold_rele_power")
+ int circ;
+
+ if ((dip == NULL) ||
+ (PM_GET_PM_INFO(dip) == NULL) || PM_ISBC(dip))
+ return;
+ PM_LOCK_POWER(dip, &circ);
+ ASSERT(cnt >= 0 && PM_KUC(dip) >= 0 || cnt < 0 && PM_KUC(dip) > 0);
+ PMD(PMD_KIDSUP, ("%s: kidsupcnt for %s@%s(%s#%d) %d->%d\n", pmf,
+ PM_DEVICE(dip), PM_KUC(dip), (PM_KUC(dip) + cnt)))
+ PM_KUC(dip) += cnt;
+ ASSERT(PM_KUC(dip) >= 0);
+ PM_UNLOCK_POWER(dip, circ);
+ if (cnt < 0 && PM_KUC(dip) == 0)
+ pm_rescan(dip);
+}
+
+#define MAX_PPM_HANDLERS 4
+
+kmutex_t ppm_lock; /* in case we ever do multi-threaded startup */
+
+struct ppm_callbacks {
+ int (*ppmc_func)(dev_info_t *);
+ dev_info_t *ppmc_dip;
+} ppm_callbacks[MAX_PPM_HANDLERS + 1];
+
+
+/*
+ * This routine calls into all the registered ppms to notify them
+ * that either all components of power-managed devices are at their
+ * lowest levels or no longer all are at their lowest levels.
+ */
+static void
+pm_ppm_notify_all_lowest(dev_info_t *dip, int mode)
+{
+ struct ppm_callbacks *ppmcp;
+ power_req_t power_req;
+ int result = 0;
+
+ power_req.request_type = PMR_PPM_ALL_LOWEST;
+ power_req.req.ppm_all_lowest_req.mode = mode;
+ mutex_enter(&ppm_lock);
+ for (ppmcp = ppm_callbacks; ppmcp->ppmc_func; ppmcp++)
+ (void) pm_ctlops((dev_info_t *)ppmcp->ppmc_dip, dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+ mutex_exit(&ppm_lock);
+}
+
+static void
+pm_set_pm_info(dev_info_t *dip, void *value)
+{
+ DEVI(dip)->devi_pm_info = value;
+}
+
+pm_rsvp_t *pm_blocked_list;
+
+/*
+ * Look up an entry in the blocked list by dip and component
+ */
+static pm_rsvp_t *
+pm_rsvp_lookup(dev_info_t *dip, int comp)
+{
+ pm_rsvp_t *p;
+ ASSERT(MUTEX_HELD(&pm_rsvp_lock));
+ for (p = pm_blocked_list; p; p = p->pr_next)
+ if (p->pr_dip == dip && p->pr_comp == comp) {
+ return (p);
+ }
+ return (NULL);
+}
+
+/*
+ * Called when a device which is direct power managed (or the parent or
+ * dependent of such a device) changes power, or when a pm clone is closed
+ * that was direct power managing a device. This call results in pm_blocked()
+ * (below) returning.
+ */
+void
+pm_proceed(dev_info_t *dip, int cmd, int comp, int newlevel)
+{
+ PMD_FUNC(pmf, "proceed")
+ pm_rsvp_t *found = NULL;
+ pm_rsvp_t *p;
+
+ mutex_enter(&pm_rsvp_lock);
+ switch (cmd) {
+ /*
+ * we're giving up control, let any pending op continue
+ */
+ case PMP_RELEASE:
+ for (p = pm_blocked_list; p; p = p->pr_next) {
+ if (dip == p->pr_dip) {
+ p->pr_retval = PMP_RELEASE;
+ PMD(PMD_DPM, ("%s: RELEASE %s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(dip)))
+ cv_signal(&p->pr_cv);
+ }
+ }
+ break;
+
+ /*
+ * process has done PM_SET_CURRENT_POWER; let a matching request
+ * succeed and a non-matching request for the same device fail
+ */
+ case PMP_SETPOWER:
+ found = pm_rsvp_lookup(dip, comp);
+ if (!found) /* if driver not waiting */
+ break;
+ /*
+ * This cannot be pm_lower_power, since that can only happen
+ * during detach or probe
+ */
+ if (found->pr_newlevel <= newlevel) {
+ found->pr_retval = PMP_SUCCEED;
+ PMD(PMD_DPM, ("%s: SUCCEED %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+ } else {
+ found->pr_retval = PMP_FAIL;
+ PMD(PMD_DPM, ("%s: FAIL %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+ }
+ cv_signal(&found->pr_cv);
+ break;
+
+ default:
+ panic("pm_proceed unknown cmd %d", cmd);
+ }
+ mutex_exit(&pm_rsvp_lock);
+}
+
+/*
+ * This routine dispatches new work to the dependency thread. Caller must
+ * be prepared to block for memory if necessary.
+ */
+void
+pm_dispatch_to_dep_thread(int cmd, char *keeper, char *kept, int wait,
+ int *res, int cached_pwr)
+{
+ pm_dep_wk_t *new_work;
+
+ new_work = kmem_zalloc(sizeof (pm_dep_wk_t), KM_SLEEP);
+ new_work->pdw_type = cmd;
+ new_work->pdw_wait = wait;
+ new_work->pdw_done = 0;
+ new_work->pdw_ret = 0;
+ new_work->pdw_pwr = cached_pwr;
+ cv_init(&new_work->pdw_cv, NULL, CV_DEFAULT, NULL);
+ if (keeper != NULL) {
+ new_work->pdw_keeper = kmem_zalloc(strlen(keeper) + 1,
+ KM_SLEEP);
+ (void) strcpy(new_work->pdw_keeper, keeper);
+ }
+ if (kept != NULL) {
+ new_work->pdw_kept = kmem_zalloc(strlen(kept) + 1, KM_SLEEP);
+ (void) strcpy(new_work->pdw_kept, kept);
+ }
+ mutex_enter(&pm_dep_thread_lock);
+ if (pm_dep_thread_workq == NULL) {
+ pm_dep_thread_workq = new_work;
+ pm_dep_thread_tail = new_work;
+ new_work->pdw_next = NULL;
+ } else {
+ pm_dep_thread_tail->pdw_next = new_work;
+ pm_dep_thread_tail = new_work;
+ new_work->pdw_next = NULL;
+ }
+ cv_signal(&pm_dep_thread_cv);
+ /* If caller asked for it, wait till it is done. */
+ if (wait) {
+ while (!new_work->pdw_done)
+ cv_wait(&new_work->pdw_cv, &pm_dep_thread_lock);
+ /*
+ * Pass return status, if any, back.
+ */
+ if (res != NULL)
+ *res = new_work->pdw_ret;
+ /*
+ * If we asked to wait, it is our job to free the request
+ * structure.
+ */
+ if (new_work->pdw_keeper)
+ kmem_free(new_work->pdw_keeper,
+ strlen(new_work->pdw_keeper) + 1);
+ if (new_work->pdw_kept)
+ kmem_free(new_work->pdw_kept,
+ strlen(new_work->pdw_kept) + 1);
+ kmem_free(new_work, sizeof (pm_dep_wk_t));
+ }
+ mutex_exit(&pm_dep_thread_lock);
+}
+
+/*
+ * Release the pm resource for this device.
+ */
+void
+pm_rem_info(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "rem_info")
+ int i, count = 0;
+ pm_info_t *info = PM_GET_PM_INFO(dip);
+ dev_info_t *pdip = ddi_get_parent(dip);
+ char *pathbuf;
+ int work_type = PM_DEP_WK_DETACH;
+
+ ASSERT(info);
+
+ ASSERT(!PM_IAM_LOCKING_DIP(dip));
+ if (PM_ISDIRECT(dip)) {
+ info->pmi_dev_pm_state &= ~PM_DIRECT;
+ ASSERT(info->pmi_clone);
+ info->pmi_clone = 0;
+ pm_proceed(dip, PMP_RELEASE, -1, -1);
+ }
+ ASSERT(!PM_GET_PM_SCAN(dip));
+
+ /*
+ * Now adjust parent's kidsupcnt. BC nodes we check only comp 0,
+ * Others we check all components. BC node that has already
+ * called pm_destroy_components() has zero component count.
+ * Parents that get notification are not adjusted because their
+ * kidsupcnt is always 0 (or 1 during configuration).
+ */
+ PMD(PMD_KEEPS, ("%s: %s@%s(%s#%d) has %d components\n", pmf,
+ PM_DEVICE(dip), PM_NUMCMPTS(dip)))
+
+ /* node is detached, so we can examine power without locking */
+ if (PM_ISBC(dip)) {
+ count = (PM_CURPOWER(dip, 0) != 0);
+ } else {
+ for (i = 0; i < PM_NUMCMPTS(dip); i++)
+ count += (PM_CURPOWER(dip, i) != 0);
+ }
+
+ if (PM_NUMCMPTS(dip) && pdip && !PM_WANTS_NOTIFICATION(pdip))
+ e_pm_hold_rele_power(pdip, -count);
+
+ /* Schedule a request to clean up dependency records */
+ pathbuf = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+ pm_dispatch_to_dep_thread(work_type, pathbuf, pathbuf,
+ PM_DEP_NOWAIT, NULL, (count > 0));
+ kmem_free(pathbuf, MAXPATHLEN);
+
+ /*
+ * Adjust the pm_comps_notlowest count since this device is
+ * not being power-managed anymore.
+ */
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ if (PM_CURPOWER(dip, i) != 0)
+ PM_DECR_NOTLOWEST(dip);
+ }
+ /*
+ * Once we clear the info pointer, it looks like it is not power
+ * managed to everybody else.
+ */
+ pm_set_pm_info(dip, NULL);
+ kmem_free(info, sizeof (pm_info_t));
+}
+
+int
+pm_get_norm_pwrs(dev_info_t *dip, int **valuep, size_t *length)
+{
+ int components = PM_NUMCMPTS(dip);
+ int *bufp;
+ size_t size;
+ int i;
+
+ if (components <= 0) {
+ cmn_err(CE_NOTE, "!pm: %s@%s(%s#%d) has no components, "
+ "can't get normal power values\n", PM_DEVICE(dip));
+ return (DDI_FAILURE);
+ } else {
+ size = components * sizeof (int);
+ bufp = kmem_alloc(size, KM_SLEEP);
+ for (i = 0; i < components; i++) {
+ bufp[i] = pm_get_normal_power(dip, i);
+ }
+ }
+ *length = size;
+ *valuep = bufp;
+ return (DDI_SUCCESS);
+}
+
+static int
+pm_reset_timestamps(dev_info_t *dip, void *arg)
+{
+ _NOTE(ARGUNUSED(arg))
+
+ int components;
+ int i;
+
+ if (!PM_GET_PM_INFO(dip))
+ return (DDI_WALK_CONTINUE);
+ components = PM_NUMCMPTS(dip);
+ ASSERT(components > 0);
+ PM_LOCK_BUSY(dip);
+ for (i = 0; i < components; i++) {
+ struct pm_component *cp;
+ /*
+ * If the component was not marked as busy,
+ * reset its timestamp to now.
+ */
+ cp = PM_CP(dip, i);
+ if (cp->pmc_timestamp)
+ cp->pmc_timestamp = gethrestime_sec();
+ }
+ PM_UNLOCK_BUSY(dip);
+ return (DDI_WALK_CONTINUE);
+}
+
+/*
+ * Convert a power level to an index into the levels array (or
+ * just PM_LEVEL_UNKNOWN in that special case).
+ */
+static int
+pm_level_to_index(dev_info_t *dip, pm_component_t *cp, int level)
+{
+ PMD_FUNC(pmf, "level_to_index")
+ int i;
+ int limit = cp->pmc_comp.pmc_numlevels;
+ int *ip = cp->pmc_comp.pmc_lvals;
+
+ if (level == PM_LEVEL_UNKNOWN)
+ return (level);
+
+ for (i = 0; i < limit; i++) {
+ if (level == *ip++) {
+ PMD(PMD_LEVEL, ("%s: %s@%s(%s#%d)[%d] to %x\n",
+ pmf, PM_DEVICE(dip),
+ (int)(cp - DEVI(dip)->devi_pm_components), level))
+ return (i);
+ }
+ }
+ panic("pm_level_to_index: level %d not found for device "
+ "%s@%s(%s#%d)", level, PM_DEVICE(dip));
+ /*NOTREACHED*/
+}
+
+/*
+ * Internal function to set current power level
+ */
+static void
+e_pm_set_cur_pwr(dev_info_t *dip, pm_component_t *cp, int level)
+{
+ PMD_FUNC(pmf, "set_cur_pwr")
+ int curpwr = (cp->pmc_flags & PM_PHC_WHILE_SET_POWER ?
+ cp->pmc_phc_pwr : cp->pmc_cur_pwr);
+
+ /*
+ * Nothing to adjust if current & new levels are the same.
+ */
+ if (curpwr != PM_LEVEL_UNKNOWN &&
+ level == cp->pmc_comp.pmc_lvals[curpwr])
+ return;
+
+ /*
+ * Keep the count for comps doing transition to/from lowest
+ * level.
+ */
+ if (curpwr == 0) {
+ PM_INCR_NOTLOWEST(dip);
+ } else if (level == cp->pmc_comp.pmc_lvals[0]) {
+ PM_DECR_NOTLOWEST(dip);
+ }
+ cp->pmc_phc_pwr = PM_LEVEL_UNKNOWN;
+ cp->pmc_cur_pwr = pm_level_to_index(dip, cp, level);
+}
+
+/*
+ * This is the default method of setting the power of a device if no ppm
+ * driver has claimed it.
+ */
+int
+pm_power(dev_info_t *dip, int comp, int level)
+{
+ PMD_FUNC(pmf, "power")
+ struct dev_ops *ops;
+ int (*fn)(dev_info_t *, int, int);
+ struct pm_component *cp = PM_CP(dip, comp);
+ int retval;
+ pm_info_t *info = PM_GET_PM_INFO(dip);
+ static int pm_phc_impl(dev_info_t *, int, int, int);
+
+ PMD(PMD_KIDSUP, ("%s: %s@%s(%s#%d), comp=%d, level=%d\n", pmf,
+ PM_DEVICE(dip), comp, level))
+ if (!(ops = ddi_get_driver(dip))) {
+ PMD(PMD_FAIL, ("%s: %s@%s(%s#%d) has no ops\n", pmf,
+ PM_DEVICE(dip)))
+ return (DDI_FAILURE);
+ }
+ if ((ops->devo_rev < 2) || !(fn = ops->devo_power)) {
+ PMD(PMD_FAIL, ("%s: %s%s\n", pmf,
+ (ops->devo_rev < 2 ? " wrong devo_rev" : ""),
+ (!fn ? " devo_power NULL" : "")))
+ return (DDI_FAILURE);
+ }
+ cp->pmc_flags |= PM_POWER_OP;
+ retval = (*fn)(dip, comp, level);
+ cp->pmc_flags &= ~PM_POWER_OP;
+ if (retval == DDI_SUCCESS) {
+ e_pm_set_cur_pwr(dip, PM_CP(dip, comp), level);
+ return (DDI_SUCCESS);
+ }
+
+ /*
+ * If pm_power_has_changed() detected a deadlock with pm_power() it
+ * updated only the power level of the component. If our attempt to
+ * set the device new to a power level above has failed we sync the
+ * total power state via phc code now.
+ */
+ if (cp->pmc_flags & PM_PHC_WHILE_SET_POWER) {
+ int phc_lvl =
+ cp->pmc_comp.pmc_lvals[cp->pmc_cur_pwr];
+
+ ASSERT(info);
+ (void) pm_phc_impl(dip, comp, phc_lvl, 0);
+ PMD(PMD_PHC, ("%s: phc %s@%s(%s#%d) comp=%d level=%d\n",
+ pmf, PM_DEVICE(dip), comp, phc_lvl))
+ }
+
+ PMD(PMD_FAIL, ("%s: can't set comp=%d (%s) of %s@%s(%s#%d) to "
+ "level=%d (%s)\n", pmf, comp, cp->pmc_comp.pmc_name, PM_DEVICE(dip),
+ level, power_val_to_string(cp, level)));
+ return (DDI_FAILURE);
+}
+
+int
+pm_unmanage(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "unmanage")
+ power_req_t power_req;
+ int result, retval = 0;
+
+ ASSERT(!PM_IAM_LOCKING_DIP(dip));
+ PMD(PMD_REMDEV | PMD_KIDSUP, ("%s: %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+ power_req.request_type = PMR_PPM_UNMANAGE;
+ power_req.req.ppm_config_req.who = dip;
+ if (pm_ppm_claimed(dip))
+ retval = pm_ctlops(PPM(dip), dip, DDI_CTLOPS_POWER,
+ &power_req, &result);
+#ifdef DEBUG
+ else
+ retval = pm_ctlops(PPM(dip), dip, DDI_CTLOPS_POWER,
+ &power_req, &result);
+#endif
+ ASSERT(retval == DDI_SUCCESS);
+ pm_rem_info(dip);
+ return (retval);
+}
+
+int
+pm_raise_power(dev_info_t *dip, int comp, int level)
+{
+ if (level < 0)
+ return (DDI_FAILURE);
+ if (!e_pm_valid_info(dip, NULL) || !e_pm_valid_comp(dip, comp, NULL) ||
+ !e_pm_valid_power(dip, comp, level))
+ return (DDI_FAILURE);
+
+ return (dev_is_needed(dip, comp, level, PM_LEVEL_UPONLY));
+}
+
+int
+pm_lower_power(dev_info_t *dip, int comp, int level)
+{
+ PMD_FUNC(pmf, "pm_lower_power")
+
+ if (!e_pm_valid_info(dip, NULL) || !e_pm_valid_comp(dip, comp, NULL) ||
+ !e_pm_valid_power(dip, comp, level)) {
+ PMD(PMD_FAIL, ("%s: validation checks failed for %s@%s(%s#%d) "
+ "comp=%d level=%d\n", pmf, PM_DEVICE(dip), comp, level))
+ return (DDI_FAILURE);
+ }
+
+ if (!DEVI_IS_DETACHING(dip)) {
+ PMD(PMD_FAIL, ("%s: %s@%s(%s#%d) not detaching\n",
+ pmf, PM_DEVICE(dip)))
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * If we don't care about saving power, or we're treating this node
+ * specially, then this is a no-op
+ */
+ if (!autopm_enabled || pm_noinvol(dip)) {
+ PMD(PMD_FAIL, ("%s: %s@%s(%s#%d) %s%s\n", pmf, PM_DEVICE(dip),
+ !autopm_enabled ? "!autopm_enabled " : "",
+ pm_noinvol(dip) ? "pm_noinvol()" : ""))
+ return (DDI_SUCCESS);
+ }
+
+ if (dev_is_needed(dip, comp, level, PM_LEVEL_DOWNONLY) != DDI_SUCCESS) {
+ PMD(PMD_FAIL, ("%s: %s@%s(%s#%d) dev_is_needed failed\n", pmf,
+ PM_DEVICE(dip)))
+ return (DDI_FAILURE);
+ }
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Find the entries struct for a given dip in the blocked list, return it locked
+ */
+static psce_t *
+pm_psc_dip_to_direct(dev_info_t *dip, pscc_t **psccp)
+{
+ pscc_t *p;
+ psce_t *psce;
+
+ rw_enter(&pm_pscc_direct_rwlock, RW_READER);
+ for (p = pm_pscc_direct; p; p = p->pscc_next) {
+ if (p->pscc_dip == dip) {
+ *psccp = p;
+ psce = p->pscc_entries;
+ mutex_enter(&psce->psce_lock);
+ ASSERT(psce);
+ rw_exit(&pm_pscc_direct_rwlock);
+ return (psce);
+ }
+ }
+ rw_exit(&pm_pscc_direct_rwlock);
+ panic("sunpm: no entry for dip %p in direct list", (void *)dip);
+ /*NOTREACHED*/
+}
+
+/*
+ * Write an entry indicating a power level change (to be passed to a process
+ * later) in the given psce.
+ * If we were called in the path that brings up the console fb in the
+ * case of entering the prom, we don't want to sleep. If the alloc fails, then
+ * we create a record that has a size of -1, a physaddr of NULL, and that
+ * has the overflow flag set.
+ */
+static int
+psc_entry(ushort_t event, psce_t *psce, dev_info_t *dip, int comp, int new,
+ int old, int which, pm_canblock_t canblock)
+{
+ char buf[MAXNAMELEN];
+ pm_state_change_t *p;
+ size_t size;
+ caddr_t physpath = NULL;
+ int overrun = 0;
+
+ ASSERT(MUTEX_HELD(&psce->psce_lock));
+ (void) ddi_pathname(dip, buf);
+ size = strlen(buf) + 1;
+ p = psce->psce_in;
+ if (canblock == PM_CANBLOCK_BYPASS) {
+ physpath = kmem_alloc(size, KM_NOSLEEP);
+ if (physpath == NULL) {
+ /*
+ * mark current entry as overrun
+ */
+ p->flags |= PSC_EVENT_LOST;
+ size = (size_t)-1;
+ }
+ } else
+ physpath = kmem_alloc(size, KM_SLEEP);
+ if (p->size) { /* overflow; mark the next entry */
+ if (p->size != (size_t)-1)
+ kmem_free(p->physpath, p->size);
+ ASSERT(psce->psce_out == p);
+ if (p == psce->psce_last) {
+ psce->psce_first->flags |= PSC_EVENT_LOST;
+ psce->psce_out = psce->psce_first;
+ } else {
+ (p + 1)->flags |= PSC_EVENT_LOST;
+ psce->psce_out = (p + 1);
+ }
+ overrun++;
+ } else if (physpath == NULL) { /* alloc failed, mark this entry */
+ p->flags |= PSC_EVENT_LOST;
+ p->size = 0;
+ p->physpath = NULL;
+ }
+ if (which == PSC_INTEREST) {
+ mutex_enter(&pm_compcnt_lock);
+ if (pm_comps_notlowest == 0)
+ p->flags |= PSC_ALL_LOWEST;
+ else
+ p->flags &= ~PSC_ALL_LOWEST;
+ mutex_exit(&pm_compcnt_lock);
+ }
+ p->event = event;
+ p->timestamp = gethrestime_sec();
+ p->component = comp;
+ p->old_level = old;
+ p->new_level = new;
+ p->physpath = physpath;
+ p->size = size;
+ if (physpath != NULL)
+ (void) strcpy(p->physpath, buf);
+ if (p == psce->psce_last)
+ psce->psce_in = psce->psce_first;
+ else
+ psce->psce_in = ++p;
+ mutex_exit(&psce->psce_lock);
+ return (overrun);
+}
+
+/*
+ * Find the next entry on the interest list. We keep a pointer to the item we
+ * last returned in the user's cooke. Returns a locked entries struct.
+ */
+static psce_t *
+psc_interest(void **cookie, pscc_t **psccp)
+{
+ pscc_t *pscc;
+ pscc_t **cookiep = (pscc_t **)cookie;
+
+ if (*cookiep == NULL)
+ pscc = pm_pscc_interest;
+ else
+ pscc = (*cookiep)->pscc_next;
+ if (pscc) {
+ *cookiep = pscc;
+ *psccp = pscc;
+ mutex_enter(&pscc->pscc_entries->psce_lock);
+ return (pscc->pscc_entries);
+ } else {
+ return (NULL);
+ }
+}
+
+/*
+ * Create an entry for a process to pick up indicating a power level change.
+ */
+static void
+pm_enqueue_notify(ushort_t cmd, dev_info_t *dip, int comp,
+ int newlevel, int oldlevel, pm_canblock_t canblock)
+{
+ PMD_FUNC(pmf, "enqueue_notify")
+ pscc_t *pscc;
+ psce_t *psce;
+ void *cookie = NULL;
+ int overrun;
+
+ ASSERT(MUTEX_HELD(&pm_rsvp_lock));
+ switch (cmd) {
+ case PSC_PENDING_CHANGE: /* only for controlling process */
+ PMD(PMD_DPM, ("%s: PENDING %s@%s(%s#%d), comp %d, %d -> %d\n",
+ pmf, PM_DEVICE(dip), comp, oldlevel, newlevel))
+ psce = pm_psc_dip_to_direct(dip, &pscc);
+ ASSERT(psce);
+ PMD(PMD_IOCTL, ("%s: PENDING: %s@%s(%s#%d) pm_poll_cnt[%d] "
+ "%d\n", pmf, PM_DEVICE(dip), pscc->pscc_clone,
+ pm_poll_cnt[pscc->pscc_clone]))
+ overrun = psc_entry(cmd, psce, dip, comp, newlevel, oldlevel,
+ PSC_DIRECT, canblock);
+ PMD(PMD_DPM, ("%s: sig %d\n", pmf, pscc->pscc_clone))
+ mutex_enter(&pm_clone_lock);
+ if (!overrun)
+ pm_poll_cnt[pscc->pscc_clone]++;
+ cv_signal(&pm_clones_cv[pscc->pscc_clone]);
+ pollwakeup(&pm_pollhead, (POLLRDNORM | POLLIN));
+ mutex_exit(&pm_clone_lock);
+ break;
+ case PSC_HAS_CHANGED:
+ PMD(PMD_DPM, ("%s: HAS %s@%s(%s#%d), comp %d, %d -> %d\n",
+ pmf, PM_DEVICE(dip), comp, oldlevel, newlevel))
+ if (PM_ISDIRECT(dip) && canblock != PM_CANBLOCK_BYPASS) {
+ psce = pm_psc_dip_to_direct(dip, &pscc);
+ PMD(PMD_IOCTL, ("%s: HAS: %s@%s(%s#%d) pm_poll_cnt[%d] "
+ "%d\n", pmf, PM_DEVICE(dip), pscc->pscc_clone,
+ pm_poll_cnt[pscc->pscc_clone]))
+ overrun = psc_entry(cmd, psce, dip, comp, newlevel,
+ oldlevel, PSC_DIRECT, canblock);
+ PMD(PMD_DPM, ("%s: sig %d\n", pmf, pscc->pscc_clone))
+ mutex_enter(&pm_clone_lock);
+ if (!overrun)
+ pm_poll_cnt[pscc->pscc_clone]++;
+ cv_signal(&pm_clones_cv[pscc->pscc_clone]);
+ pollwakeup(&pm_pollhead, (POLLRDNORM | POLLIN));
+ mutex_exit(&pm_clone_lock);
+ }
+ mutex_enter(&pm_clone_lock);
+ rw_enter(&pm_pscc_interest_rwlock, RW_READER);
+ while ((psce = psc_interest(&cookie, &pscc)) != NULL) {
+ (void) psc_entry(cmd, psce, dip, comp, newlevel,
+ oldlevel, PSC_INTEREST, canblock);
+ cv_signal(&pm_clones_cv[pscc->pscc_clone]);
+ }
+ rw_exit(&pm_pscc_interest_rwlock);
+ mutex_exit(&pm_clone_lock);
+ break;
+#ifdef DEBUG
+ default:
+ ASSERT(0);
+#endif
+ }
+}
+
+static void
+pm_enqueue_notify_others(pm_ppm_devlist_t **listp, pm_canblock_t canblock)
+{
+ if (listp) {
+ pm_ppm_devlist_t *p, *next = NULL;
+
+ for (p = *listp; p; p = next) {
+ next = p->ppd_next;
+ pm_enqueue_notify(PSC_HAS_CHANGED, p->ppd_who,
+ p->ppd_cmpt, p->ppd_new_level, p->ppd_old_level,
+ canblock);
+ kmem_free(p, sizeof (pm_ppm_devlist_t));
+ }
+ *listp = NULL;
+ }
+}
+
+/*
+ * Try to get the power locks of the parent node and target (child)
+ * node. Return true if successful (with both locks held) or false
+ * (with no locks held).
+ */
+static int
+pm_try_parent_child_locks(dev_info_t *pdip,
+ dev_info_t *dip, int *pcircp, int *circp)
+{
+ if (ndi_devi_tryenter(pdip, pcircp))
+ if (PM_TRY_LOCK_POWER(dip, circp)) {
+ return (1);
+ } else {
+ ndi_devi_exit(pdip, *pcircp);
+ }
+ return (0);
+}
+
+/*
+ * Determine if the power lock owner is blocked by current thread.
+ * returns :
+ * 1 - If the thread owning the effective power lock (the first lock on
+ * which a thread blocks when it does PM_LOCK_POWER) is blocked by
+ * a mutex held by the current thread.
+ *
+ * 0 - otherwise
+ *
+ * Note : This function is called by pm_power_has_changed to determine whether
+ * it is executing in parallel with pm_set_power.
+ */
+static int
+pm_blocked_by_us(dev_info_t *dip)
+{
+ power_req_t power_req;
+ kthread_t *owner;
+ int result;
+ kmutex_t *mp;
+ dev_info_t *ppm = (dev_info_t *)DEVI(dip)->devi_pm_ppm;
+
+ power_req.request_type = PMR_PPM_POWER_LOCK_OWNER;
+ power_req.req.ppm_power_lock_owner_req.who = dip;
+ if (pm_ctlops(ppm, dip, DDI_CTLOPS_POWER, &power_req, &result) !=
+ DDI_SUCCESS) {
+ /*
+ * It is assumed that if the device is claimed by ppm, ppm
+ * will always implement this request type and it'll always
+ * return success. We panic here, if it fails.
+ */
+ panic("pm: Can't determine power lock owner of %s@%s(%s#%d)\n",
+ PM_DEVICE(dip));
+ /*NOTREACHED*/
+ }
+
+ if ((owner = power_req.req.ppm_power_lock_owner_req.owner) != NULL &&
+ owner->t_state == TS_SLEEP &&
+ owner->t_sobj_ops &&
+ SOBJ_TYPE(owner->t_sobj_ops) == SOBJ_MUTEX &&
+ (mp = (kmutex_t *)owner->t_wchan) &&
+ mutex_owner(mp) == curthread)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Notify parent which wants to hear about a child's power changes.
+ */
+static void
+pm_notify_parent(dev_info_t *dip,
+ dev_info_t *pdip, int comp, int old_level, int level)
+{
+ pm_bp_has_changed_t bphc;
+ pm_sp_misc_t pspm;
+ char *pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ int result = DDI_SUCCESS;
+
+ bphc.bphc_dip = dip;
+ bphc.bphc_path = ddi_pathname(dip, pathbuf);
+ bphc.bphc_comp = comp;
+ bphc.bphc_olevel = old_level;
+ bphc.bphc_nlevel = level;
+ pspm.pspm_canblock = PM_CANBLOCK_BLOCK;
+ pspm.pspm_scan = 0;
+ bphc.bphc_private = &pspm;
+ (void) (*PM_BUS_POWER_FUNC(pdip))(pdip, NULL,
+ BUS_POWER_HAS_CHANGED, (void *)&bphc, (void *)&result);
+ kmem_free(pathbuf, MAXPATHLEN);
+}
+
+/*
+ * Check if we need to resume a BC device, and make the attach call as required.
+ */
+static int
+pm_check_and_resume(dev_info_t *dip, int comp, int old_level, int level)
+{
+ int ret = DDI_SUCCESS;
+
+ if (PM_ISBC(dip) && comp == 0 && old_level == 0 && level != 0) {
+ ASSERT(DEVI(dip)->devi_pm_flags & PMC_SUSPENDED);
+ /* ppm is not interested in DDI_PM_RESUME */
+ if ((ret = devi_attach(dip, DDI_PM_RESUME)) != DDI_SUCCESS)
+ /* XXX Should we mark it resumed, */
+ /* even though it failed? */
+ cmn_err(CE_WARN, "!pm: Can't resume %s@%s",
+ PM_NAME(dip), PM_ADDR(dip));
+ DEVI(dip)->devi_pm_flags &= ~PMC_SUSPENDED;
+ }
+
+ return (ret);
+}
+
+/*
+ * Tests outside the lock to see if we should bother to enqueue an entry
+ * for any watching process. If yes, then caller will take the lock and
+ * do the full protocol
+ */
+static int
+pm_watchers()
+{
+ if (pm_processes_stopped)
+ return (0);
+ return (pm_pscc_direct || pm_pscc_interest);
+}
+
+/*
+ * A driver is reporting that the power of one of its device's components
+ * has changed. Update the power state accordingly.
+ */
+int
+pm_power_has_changed(dev_info_t *dip, int comp, int level)
+{
+ PMD_FUNC(pmf, "pm_power_has_changed")
+ int ret;
+ dev_info_t *pdip = ddi_get_parent(dip);
+ struct pm_component *cp;
+ int blocked, circ, pcirc, old_level;
+ static int pm_phc_impl(dev_info_t *, int, int, int);
+
+ if (level < 0) {
+ PMD(PMD_FAIL, ("%s: %s@%s(%s#%d): bad level=%d\n", pmf,
+ PM_DEVICE(dip), level))
+ return (DDI_FAILURE);
+ }
+
+ PMD(PMD_KIDSUP | PMD_DEP, ("%s: %s@%s(%s#%d), comp=%d, level=%d\n", pmf,
+ PM_DEVICE(dip), comp, level))
+
+ if (!e_pm_valid_info(dip, NULL) || !e_pm_valid_comp(dip, comp, &cp) ||
+ !e_pm_valid_power(dip, comp, level))
+ return (DDI_FAILURE);
+
+ /*
+ * A driver thread calling pm_power_has_changed and another thread
+ * calling pm_set_power can deadlock. The problem is not resolvable
+ * by changing lock order, so we use pm_blocked_by_us() to detect
+ * this specific deadlock. If we can't get the lock immediately
+ * and we are deadlocked, just update the component's level, do
+ * notifications, and return. We intend to update the total power
+ * state later (if the other thread fails to set power to the
+ * desired level). If we were called because of a power change on a
+ * component that isn't involved in a set_power op, update all state
+ * immediately.
+ */
+ cp = PM_CP(dip, comp);
+ while (!pm_try_parent_child_locks(pdip, dip, &pcirc, &circ)) {
+ if (((blocked = pm_blocked_by_us(dip)) != 0) &&
+ (cp->pmc_flags & PM_POWER_OP)) {
+ if (pm_watchers()) {
+ mutex_enter(&pm_rsvp_lock);
+ pm_enqueue_notify(PSC_HAS_CHANGED, dip, comp,
+ level, cur_power(cp), PM_CANBLOCK_BLOCK);
+ mutex_exit(&pm_rsvp_lock);
+ }
+ if (pdip && PM_WANTS_NOTIFICATION(pdip))
+ pm_notify_parent(dip,
+ pdip, comp, cur_power(cp), level);
+ (void) pm_check_and_resume(dip,
+ comp, cur_power(cp), level);
+
+ /*
+ * Stash the old power index, update curpwr, and flag
+ * that the total power state needs to be synched.
+ */
+ cp->pmc_flags |= PM_PHC_WHILE_SET_POWER;
+ /*
+ * Several pm_power_has_changed calls could arrive
+ * while the set power path remains blocked. Keep the
+ * oldest old power and the newest new power of any
+ * sequence of phc calls which arrive during deadlock.
+ */
+ if (cp->pmc_phc_pwr == PM_LEVEL_UNKNOWN)
+ cp->pmc_phc_pwr = cp->pmc_cur_pwr;
+ cp->pmc_cur_pwr =
+ pm_level_to_index(dip, cp, level);
+ PMD(PMD_PHC, ("%s: deadlock for %s@%s(%s#%d), comp=%d, "
+ "level=%d\n", pmf, PM_DEVICE(dip), comp, level))
+ return (DDI_SUCCESS);
+ } else
+ if (blocked) { /* blocked, but different cmpt? */
+ if (!ndi_devi_tryenter(pdip, &pcirc)) {
+ cmn_err(CE_NOTE,
+ "!pm: parent kuc not updated due "
+ "to possible deadlock.\n");
+ return (pm_phc_impl(dip,
+ comp, level, 1));
+ }
+ old_level = cur_power(cp);
+ if (pdip && !PM_WANTS_NOTIFICATION(pdip) &&
+ (!PM_ISBC(dip) || comp == 0) &&
+ POWERING_ON(old_level, level))
+ pm_hold_power(pdip);
+ ret = pm_phc_impl(dip, comp, level, 1);
+ if (pdip && !PM_WANTS_NOTIFICATION(pdip)) {
+ if ((!PM_ISBC(dip) ||
+ comp == 0) && level == 0 &&
+ old_level != PM_LEVEL_UNKNOWN)
+ pm_rele_power(pdip);
+ }
+ ndi_devi_exit(pdip, pcirc);
+ /* child lock not held: deadlock */
+ return (ret);
+ }
+ delay(1);
+ PMD(PMD_PHC, ("%s: try lock again\n", pmf))
+ }
+
+ /* non-deadlock case */
+ old_level = cur_power(cp);
+ if (pdip && !PM_WANTS_NOTIFICATION(pdip) &&
+ (!PM_ISBC(dip) || comp == 0) && POWERING_ON(old_level, level))
+ pm_hold_power(pdip);
+ ret = pm_phc_impl(dip, comp, level, 1);
+ if (pdip && !PM_WANTS_NOTIFICATION(pdip)) {
+ if ((!PM_ISBC(dip) || comp == 0) && level == 0 &&
+ old_level != PM_LEVEL_UNKNOWN)
+ pm_rele_power(pdip);
+ }
+ PM_UNLOCK_POWER(dip, circ);
+ ndi_devi_exit(pdip, pcirc);
+ return (ret);
+}
+
+/*
+ * Account for power changes to a component of the the console frame buffer.
+ * If lowering power from full (or "unkown", which is treatd as full)
+ * we will increment the "components off" count of the fb device.
+ * Subsequent lowering of the same component doesn't affect the count. If
+ * raising a component back to full power, we will decrement the count.
+ *
+ * Return: the increment value for pm_cfb_comps_off (-1, 0, or 1)
+ */
+static int
+calc_cfb_comps_incr(dev_info_t *dip, int cmpt, int old, int new)
+{
+ struct pm_component *cp = PM_CP(dip, cmpt);
+ int on = (old == PM_LEVEL_UNKNOWN || old == cp->pmc_norm_pwr);
+ int want_normal = (new == cp->pmc_norm_pwr);
+ int incr = 0;
+
+ if (on && !want_normal)
+ incr = 1;
+ else if (!on && want_normal)
+ incr = -1;
+ return (incr);
+}
+
+/*
+ * Adjust the count of console frame buffer components < full power.
+ */
+static void
+update_comps_off(int incr, dev_info_t *dip)
+{
+ mutex_enter(&pm_cfb_lock);
+ pm_cfb_comps_off += incr;
+ ASSERT(pm_cfb_comps_off <= PM_NUMCMPTS(dip));
+ mutex_exit(&pm_cfb_lock);
+}
+
+/*
+ * Update the power state in the framework (via the ppm). The 'notify'
+ * argument tells whether to notify watchers. Power lock is already held.
+ */
+static int
+pm_phc_impl(dev_info_t *dip, int comp, int level, int notify)
+{
+ PMD_FUNC(pmf, "phc_impl")
+ power_req_t power_req;
+ int i, dodeps = 0;
+ dev_info_t *pdip = ddi_get_parent(dip);
+ int result;
+ int old_level;
+ struct pm_component *cp;
+ int incr = 0;
+ dev_info_t *ppm = (dev_info_t *)DEVI(dip)->devi_pm_ppm;
+ int work_type = 0;
+ char *pathbuf;
+
+ /* Must use "official" power level for this test. */
+ cp = PM_CP(dip, comp);
+ old_level = (cp->pmc_flags & PM_PHC_WHILE_SET_POWER ?
+ cp->pmc_phc_pwr : cp->pmc_cur_pwr);
+ if (old_level != PM_LEVEL_UNKNOWN)
+ old_level = cp->pmc_comp.pmc_lvals[old_level];
+
+ if (level == old_level) {
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d), comp=%d is already at "
+ "level=%d\n", pmf, PM_DEVICE(dip), comp, level))
+ return (DDI_SUCCESS);
+ }
+
+ /*
+ * Tell ppm about this.
+ */
+ power_req.request_type = PMR_PPM_POWER_CHANGE_NOTIFY;
+ power_req.req.ppm_notify_level_req.who = dip;
+ power_req.req.ppm_notify_level_req.cmpt = comp;
+ power_req.req.ppm_notify_level_req.new_level = level;
+ power_req.req.ppm_notify_level_req.old_level = old_level;
+ if (pm_ctlops(ppm, dip, DDI_CTLOPS_POWER, &power_req,
+ &result) == DDI_FAILURE) {
+ PMD(PMD_FAIL, ("%s: pm_ctlops %s@%s(%s#%d) to %d failed\n",
+ pmf, PM_DEVICE(dip), level))
+ return (DDI_FAILURE);
+ }
+
+ if (PM_IS_CFB(dip)) {
+ incr = calc_cfb_comps_incr(dip, comp, old_level, level);
+
+ if (incr) {
+ update_comps_off(incr, dip);
+ PMD(PMD_CFB, ("%s: %s@%s(%s#%d) comp=%d %d->%d "
+ "cfb_comps_off->%d\n", pmf, PM_DEVICE(dip),
+ comp, old_level, level, pm_cfb_comps_off))
+ }
+ }
+ e_pm_set_cur_pwr(dip, PM_CP(dip, comp), level);
+ result = DDI_SUCCESS;
+
+ if (notify) {
+ if (pdip && PM_WANTS_NOTIFICATION(pdip))
+ pm_notify_parent(dip, pdip, comp, old_level, level);
+ (void) pm_check_and_resume(dip, comp, old_level, level);
+ }
+
+ /*
+ * Decrement the dependency kidsup count if we turn a device
+ * off.
+ */
+ if (POWERING_OFF(old_level, level)) {
+ dodeps = 1;
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ cp = PM_CP(dip, i);
+ if (cur_power(cp)) {
+ dodeps = 0;
+ break;
+ }
+ }
+ if (dodeps)
+ work_type = PM_DEP_WK_POWER_OFF;
+ }
+
+ /*
+ * Increment if we turn it on. Check to see
+ * if other comps are already on, if so,
+ * dont increment.
+ */
+ if (POWERING_ON(old_level, level)) {
+ dodeps = 1;
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ cp = PM_CP(dip, i);
+ if (comp == i)
+ continue;
+ /* -1 also treated as 0 in this case */
+ if (cur_power(cp) > 0) {
+ dodeps = 0;
+ break;
+ }
+ }
+ if (dodeps)
+ work_type = PM_DEP_WK_POWER_ON;
+ }
+
+ if (dodeps) {
+ pathbuf = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+ pm_dispatch_to_dep_thread(work_type, pathbuf, NULL,
+ PM_DEP_NOWAIT, NULL, 0);
+ kmem_free(pathbuf, MAXPATHLEN);
+ }
+
+ if (notify && (level != old_level) && pm_watchers()) {
+ mutex_enter(&pm_rsvp_lock);
+ pm_enqueue_notify(PSC_HAS_CHANGED, dip, comp, level, old_level,
+ PM_CANBLOCK_BLOCK);
+ mutex_exit(&pm_rsvp_lock);
+ }
+
+ PMD(PMD_RESCAN, ("%s: %s@%s(%s#%d): pm_rescan\n", pmf, PM_DEVICE(dip)))
+ pm_rescan(dip);
+ return (DDI_SUCCESS);
+}
+
+/*
+ * This function is called at startup time to notify pm of the existence
+ * of any platform power managers for this platform. As a result of
+ * this registration, each function provided will be called each time
+ * a device node is attached, until one returns true, and it must claim the
+ * device node (by returning non-zero) if it wants to be involved in the
+ * node's power management. If it does claim the node, then it will
+ * subsequently be notified of attach and detach events.
+ *
+ */
+
+int
+pm_register_ppm(int (*func)(dev_info_t *), dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "register_ppm")
+ struct ppm_callbacks *ppmcp;
+ pm_component_t *cp;
+ int i, pwr, result, circ;
+ power_req_t power_req;
+ struct ppm_notify_level_req *p = &power_req.req.ppm_notify_level_req;
+ void pm_ppm_claim(dev_info_t *);
+
+ mutex_enter(&ppm_lock);
+ ppmcp = ppm_callbacks;
+ for (i = 0; i < MAX_PPM_HANDLERS; i++, ppmcp++) {
+ if (ppmcp->ppmc_func == NULL) {
+ ppmcp->ppmc_func = func;
+ ppmcp->ppmc_dip = dip;
+ break;
+ }
+ }
+ mutex_exit(&ppm_lock);
+
+ if (i >= MAX_PPM_HANDLERS)
+ return (DDI_FAILURE);
+ while ((dip = ddi_get_parent(dip)) != NULL) {
+ if (PM_GET_PM_INFO(dip) == NULL)
+ continue;
+ pm_ppm_claim(dip);
+ if (pm_ppm_claimed(dip)) {
+ /*
+ * Tell ppm about this.
+ */
+ power_req.request_type = PMR_PPM_POWER_CHANGE_NOTIFY;
+ p->old_level = PM_LEVEL_UNKNOWN;
+ p->who = dip;
+ PM_LOCK_POWER(dip, &circ);
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ cp = PM_CP(dip, i);
+ pwr = cp->pmc_cur_pwr;
+ if (pwr != PM_LEVEL_UNKNOWN) {
+ p->cmpt = i;
+ p->new_level = cur_power(cp);
+ p->old_level = PM_LEVEL_UNKNOWN;
+ if (pm_ctlops(PPM(dip), dip,
+ DDI_CTLOPS_POWER, &power_req,
+ &result) == DDI_FAILURE) {
+ PMD(PMD_FAIL, ("%s: pc "
+ "%s@%s(%s#%d) to %d "
+ "fails\n", pmf,
+ PM_DEVICE(dip), pwr))
+ }
+ }
+ }
+ PM_UNLOCK_POWER(dip, circ);
+ }
+ }
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Call the ppm's that have registered and adjust the devinfo struct as
+ * appropriate. First one to claim it gets it. The sets of devices claimed
+ * by each ppm are assumed to be disjoint.
+ */
+void
+pm_ppm_claim(dev_info_t *dip)
+{
+ struct ppm_callbacks *ppmcp;
+
+ if (PPM(dip)) {
+ return;
+ }
+ mutex_enter(&ppm_lock);
+ for (ppmcp = ppm_callbacks; ppmcp->ppmc_func; ppmcp++) {
+ if ((*ppmcp->ppmc_func)(dip)) {
+ DEVI(dip)->devi_pm_ppm =
+ (struct dev_info *)ppmcp->ppmc_dip;
+ mutex_exit(&ppm_lock);
+ return;
+ }
+ }
+ mutex_exit(&ppm_lock);
+}
+
+/*
+ * Node is being detached so stop autopm until we see if it succeeds, in which
+ * case pm_stop will be called. For backwards compatible devices we bring the
+ * device up to full power on the assumption the detach will succeed.
+ */
+void
+pm_detaching(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "detaching")
+ pm_info_t *info = PM_GET_PM_INFO(dip);
+ int iscons;
+
+ PMD(PMD_REMDEV, ("%s: %s@%s(%s#%d), %d comps\n", pmf, PM_DEVICE(dip),
+ PM_NUMCMPTS(dip)))
+ if (info == NULL)
+ return;
+ ASSERT(DEVI_IS_DETACHING(dip));
+ PM_LOCK_DIP(dip);
+ info->pmi_dev_pm_state |= PM_DETACHING;
+ PM_UNLOCK_DIP(dip);
+ if (!PM_ISBC(dip))
+ pm_scan_stop(dip);
+
+ /*
+ * console and old-style devices get brought up when detaching.
+ */
+ iscons = PM_IS_CFB(dip);
+ if (iscons || PM_ISBC(dip)) {
+ (void) pm_all_to_normal(dip, PM_CANBLOCK_BYPASS);
+ if (iscons) {
+ mutex_enter(&pm_cfb_lock);
+ while (cfb_inuse) {
+ mutex_exit(&pm_cfb_lock);
+ PMD(PMD_CFB, ("%s: delay; cfb_inuse\n", pmf))
+ delay(1);
+ mutex_enter(&pm_cfb_lock);
+ }
+ ASSERT(cfb_dip_detaching == NULL);
+ ASSERT(cfb_dip);
+ cfb_dip_detaching = cfb_dip; /* case detach fails */
+ cfb_dip = NULL;
+ mutex_exit(&pm_cfb_lock);
+ }
+ }
+}
+
+/*
+ * Node failed to detach. If it used to be autopm'd, make it so again.
+ */
+void
+pm_detach_failed(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "detach_failed")
+ pm_info_t *info = PM_GET_PM_INFO(dip);
+ int pm_all_at_normal(dev_info_t *);
+
+ if (info == NULL)
+ return;
+ ASSERT(DEVI_IS_DETACHING(dip));
+ if (info->pmi_dev_pm_state & PM_DETACHING) {
+ info->pmi_dev_pm_state &= ~PM_DETACHING;
+ if (info->pmi_dev_pm_state & PM_ALLNORM_DEFERRED) {
+ /* Make sure the operation is still needed */
+ if (!pm_all_at_normal(dip)) {
+ if (pm_all_to_normal(dip,
+ PM_CANBLOCK_FAIL) != DDI_SUCCESS) {
+ PMD(PMD_ERROR, ("%s: could not bring "
+ "%s@%s(%s#%d) to normal\n", pmf,
+ PM_DEVICE(dip)))
+ }
+ }
+ info->pmi_dev_pm_state &= ~PM_ALLNORM_DEFERRED;
+ }
+ }
+ if (!PM_ISBC(dip)) {
+ mutex_enter(&pm_scan_lock);
+ if (autopm_enabled)
+ pm_scan_init(dip);
+ mutex_exit(&pm_scan_lock);
+ pm_rescan(dip);
+ }
+}
+
+/* generic Backwards Compatible component */
+static char *bc_names[] = {"off", "on"};
+
+static pm_comp_t bc_comp = {"unknown", 2, NULL, NULL, &bc_names[0]};
+
+static void
+e_pm_default_levels(dev_info_t *dip, pm_component_t *cp, int norm)
+{
+ pm_comp_t *pmc;
+ pmc = &cp->pmc_comp;
+ pmc->pmc_numlevels = 2;
+ pmc->pmc_lvals[0] = 0;
+ pmc->pmc_lvals[1] = norm;
+ e_pm_set_cur_pwr(dip, cp, norm);
+}
+
+static void
+e_pm_default_components(dev_info_t *dip, int cmpts)
+{
+ int i;
+ pm_component_t *p = DEVI(dip)->devi_pm_components;
+
+ p = DEVI(dip)->devi_pm_components;
+ for (i = 0; i < cmpts; i++, p++) {
+ p->pmc_comp = bc_comp; /* struct assignment */
+ p->pmc_comp.pmc_lvals = kmem_zalloc(2 * sizeof (int),
+ KM_SLEEP);
+ p->pmc_comp.pmc_thresh = kmem_alloc(2 * sizeof (int),
+ KM_SLEEP);
+ p->pmc_comp.pmc_numlevels = 2;
+ p->pmc_comp.pmc_thresh[0] = INT_MAX;
+ p->pmc_comp.pmc_thresh[1] = INT_MAX;
+ }
+}
+
+/*
+ * Called from functions that require components to exist already to allow
+ * for their creation by parsing the pm-components property.
+ * Device will not be power managed as a result of this call
+ * No locking needed because we're single threaded by the ndi_devi_enter
+ * done while attaching, and the device isn't visible until after it has
+ * attached
+ */
+int
+pm_premanage(dev_info_t *dip, int style)
+{
+ PMD_FUNC(pmf, "premanage")
+ pm_comp_t *pcp, *compp;
+ int cmpts, i, norm, error;
+ pm_component_t *p = DEVI(dip)->devi_pm_components;
+ pm_comp_t *pm_autoconfig(dev_info_t *, int *);
+
+ ASSERT(!PM_IAM_LOCKING_DIP(dip));
+ /*
+ * If this dip has already been processed, don't mess with it
+ */
+ if (DEVI(dip)->devi_pm_flags & PMC_COMPONENTS_DONE)
+ return (DDI_SUCCESS);
+ if (DEVI(dip)->devi_pm_flags & PMC_COMPONENTS_FAILED) {
+ return (DDI_FAILURE);
+ }
+ /*
+ * Look up pm-components property and create components accordingly
+ * If that fails, fall back to backwards compatibility
+ */
+ if ((compp = pm_autoconfig(dip, &error)) == NULL) {
+ /*
+ * If error is set, the property existed but was not well formed
+ */
+ if (error || (style == PM_STYLE_NEW)) {
+ DEVI(dip)->devi_pm_flags |= PMC_COMPONENTS_FAILED;
+ return (DDI_FAILURE);
+ }
+ /*
+ * If they don't have the pm-components property, then we
+ * want the old "no pm until PM_SET_DEVICE_THRESHOLDS ioctl"
+ * behavior driver must have called pm_create_components, and
+ * we need to flesh out dummy components
+ */
+ if ((cmpts = PM_NUMCMPTS(dip)) == 0) {
+ /*
+ * Not really failure, but we don't want the
+ * caller to treat it as success
+ */
+ return (DDI_FAILURE);
+ }
+ DEVI(dip)->devi_pm_flags |= PMC_BC;
+ e_pm_default_components(dip, cmpts);
+ for (i = 0; i < cmpts; i++) {
+ /*
+ * if normal power not set yet, we don't really know
+ * what *ANY* of the power values are. If normal
+ * power is set, then we assume for this backwards
+ * compatible case that the values are 0, normal power.
+ */
+ norm = pm_get_normal_power(dip, i);
+ if (norm == (uint_t)-1) {
+ PMD(PMD_ERROR, ("%s: %s@%s(%s#%d)[%d]\n", pmf,
+ PM_DEVICE(dip), i))
+ return (DDI_FAILURE);
+ }
+ /*
+ * Components of BC devices start at their normal power,
+ * so count them to be not at their lowest power.
+ */
+ PM_INCR_NOTLOWEST(dip);
+ e_pm_default_levels(dip, PM_CP(dip, i), norm);
+ }
+ } else {
+ /*
+ * e_pm_create_components was called from pm_autoconfig(), it
+ * creates components with no descriptions (or known levels)
+ */
+ cmpts = PM_NUMCMPTS(dip);
+ ASSERT(cmpts != 0);
+ pcp = compp;
+ p = DEVI(dip)->devi_pm_components;
+ for (i = 0; i < cmpts; i++, p++) {
+ p->pmc_comp = *pcp++; /* struct assignment */
+ ASSERT(PM_CP(dip, i)->pmc_cur_pwr == 0);
+ e_pm_set_cur_pwr(dip, PM_CP(dip, i), PM_LEVEL_UNKNOWN);
+ }
+ pm_set_device_threshold(dip, pm_system_idle_threshold,
+ PMC_DEF_THRESH);
+ kmem_free(compp, cmpts * sizeof (pm_comp_t));
+ }
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Called from during or after the device's attach to let us know it is ready
+ * to play autopm. Look up the pm model and manage the device accordingly.
+ * Returns system call errno value.
+ * If DDI_ATTACH and DDI_DETACH were in same namespace, this would be
+ * a little cleaner
+ *
+ * Called with dip lock held, return with dip lock unheld.
+ */
+
+int
+e_pm_manage(dev_info_t *dip, int style)
+{
+ PMD_FUNC(pmf, "e_manage")
+ pm_info_t *info;
+ dev_info_t *pdip = ddi_get_parent(dip);
+ int pm_thresh_specd(dev_info_t *);
+ int count;
+ char *pathbuf;
+
+ if (pm_premanage(dip, style) != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+ PMD(PMD_KIDSUP, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ ASSERT(PM_GET_PM_INFO(dip) == NULL);
+ info = kmem_zalloc(sizeof (pm_info_t), KM_SLEEP);
+
+ /*
+ * Now set up parent's kidsupcnt. BC nodes are assumed to start
+ * out at their normal power, so they are "up", others start out
+ * unknown, which is effectively "up". Parent which want notification
+ * get kidsupcnt of 0 always.
+ */
+ count = (PM_ISBC(dip)) ? 1 : PM_NUMCMPTS(dip);
+ if (count && pdip && !PM_WANTS_NOTIFICATION(pdip))
+ e_pm_hold_rele_power(pdip, count);
+
+ pm_set_pm_info(dip, info);
+ /*
+ * Apply any recorded thresholds
+ */
+ (void) pm_thresh_specd(dip);
+
+ /*
+ * Do dependency processing.
+ */
+ pathbuf = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+ pm_dispatch_to_dep_thread(PM_DEP_WK_ATTACH, pathbuf, pathbuf,
+ PM_DEP_NOWAIT, NULL, 0);
+ kmem_free(pathbuf, MAXPATHLEN);
+
+ if (!PM_ISBC(dip)) {
+ mutex_enter(&pm_scan_lock);
+ if (autopm_enabled) {
+ pm_scan_init(dip);
+ mutex_exit(&pm_scan_lock);
+ pm_rescan(dip);
+ } else {
+ mutex_exit(&pm_scan_lock);
+ }
+ }
+ return (0);
+}
+
+/*
+ * This is the obsolete exported interface for a driver to find out its
+ * "normal" (max) power.
+ * We only get components destroyed while no power management is
+ * going on (and the device is detached), so we don't need a mutex here
+ */
+int
+pm_get_normal_power(dev_info_t *dip, int comp)
+{
+
+ if (comp >= 0 && comp < PM_NUMCMPTS(dip)) {
+ return (PM_CP(dip, comp)->pmc_norm_pwr);
+ }
+ return (DDI_FAILURE);
+}
+
+/*
+ * Fetches the current power level. Return DDI_SUCCESS or DDI_FAILURE.
+ */
+int
+pm_get_current_power(dev_info_t *dip, int comp, int *levelp)
+{
+ if (comp >= 0 && comp < PM_NUMCMPTS(dip)) {
+ *levelp = PM_CURPOWER(dip, comp);
+ return (DDI_SUCCESS);
+ }
+ return (DDI_FAILURE);
+}
+
+/*
+ * Returns current threshold of indicated component
+ */
+static int
+cur_threshold(dev_info_t *dip, int comp)
+{
+ pm_component_t *cp = PM_CP(dip, comp);
+ int pwr;
+
+ if (PM_ISBC(dip)) {
+ /*
+ * backwards compatible nodes only have one threshold
+ */
+ return (cp->pmc_comp.pmc_thresh[1]);
+ }
+ pwr = cp->pmc_cur_pwr;
+ if (pwr == PM_LEVEL_UNKNOWN) {
+ int thresh;
+ if (DEVI(dip)->devi_pm_flags & PMC_NEXDEF_THRESH)
+ thresh = pm_default_nexus_threshold;
+ else
+ thresh = pm_system_idle_threshold;
+ return (thresh);
+ }
+ ASSERT(cp->pmc_comp.pmc_thresh);
+ return (cp->pmc_comp.pmc_thresh[pwr]);
+}
+
+/*
+ * Compute next lower component power level given power index.
+ */
+static int
+pm_next_lower_power(pm_component_t *cp, int pwrndx)
+{
+ int nxt_pwr;
+
+ if (pwrndx == PM_LEVEL_UNKNOWN) {
+ nxt_pwr = cp->pmc_comp.pmc_lvals[0];
+ } else {
+ pwrndx--;
+ ASSERT(pwrndx >= 0);
+ nxt_pwr = cp->pmc_comp.pmc_lvals[pwrndx];
+ }
+ return (nxt_pwr);
+}
+
+/*
+ * Bring all components of device to normal power
+ */
+int
+pm_all_to_normal(dev_info_t *dip, pm_canblock_t canblock)
+{
+ PMD_FUNC(pmf, "all_to_normal")
+ int *normal;
+ int i, ncomps, result;
+ size_t size;
+ int changefailed = 0;
+
+ PMD(PMD_ALLNORM, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ ASSERT(PM_GET_PM_INFO(dip));
+ if (pm_get_norm_pwrs(dip, &normal, &size) != DDI_SUCCESS) {
+ PMD(PMD_ALLNORM, ("%s: can't get norm pwrs for "
+ "%s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ return (DDI_FAILURE);
+ }
+ ncomps = PM_NUMCMPTS(dip);
+ for (i = 0; i < ncomps; i++) {
+ if (pm_set_power(dip, i, normal[i],
+ PM_LEVEL_UPONLY, canblock, 0, &result) != DDI_SUCCESS) {
+ changefailed++;
+ PMD(PMD_ALLNORM | PMD_FAIL, ("%s: failed to set "
+ "%s@%s(%s#%d)[%d] to %d, errno %d\n", pmf,
+ PM_DEVICE(dip), i, normal[i], result))
+ }
+ }
+ kmem_free(normal, size);
+ if (changefailed) {
+ PMD(PMD_FAIL, ("%s: failed to set %d comps %s@%s(%s#%d) "
+ "to full power\n", pmf, changefailed, PM_DEVICE(dip)))
+ return (DDI_FAILURE);
+ }
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Returns true if all components of device are at normal power
+ */
+int
+pm_all_at_normal(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "all_at_normal")
+ int *normal;
+ int i;
+ size_t size;
+
+ PMD(PMD_ALLNORM, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ if (pm_get_norm_pwrs(dip, &normal, &size) != DDI_SUCCESS) {
+ PMD(PMD_ALLNORM, ("%s: can't get normal power\n", pmf))
+ return (DDI_FAILURE);
+ }
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ int current = PM_CURPOWER(dip, i);
+ if (normal[i] > current) {
+ PMD(PMD_ALLNORM, ("%s: %s@%s(%s#%d) comp=%d, "
+ "norm=%d, cur=%d\n", pmf, PM_DEVICE(dip), i,
+ normal[i], current))
+ break;
+ }
+ }
+ kmem_free(normal, size);
+ if (i != PM_NUMCMPTS(dip)) {
+ return (0);
+ }
+ return (1);
+}
+
+static void
+bring_wekeeps_up(char *keeper)
+{
+ PMD_FUNC(pmf, "bring_wekeeps_up")
+ int i;
+ pm_pdr_t *dp;
+ pm_info_t *wku_info;
+ char *kept_path;
+ dev_info_t *kept;
+ static void bring_pmdep_up(dev_info_t *, int);
+
+ if (panicstr) {
+ return;
+ }
+ /*
+ * We process the request even if the keeper detaches because
+ * detach processing expects this to increment kidsupcnt of kept.
+ */
+ PMD(PMD_BRING, ("%s: keeper= %s\n", pmf, keeper))
+ for (dp = pm_dep_head; dp; dp = dp->pdr_next) {
+ if (strcmp(dp->pdr_keeper, keeper) != 0)
+ continue;
+ for (i = 0; i < dp->pdr_kept_count; i++) {
+ kept_path = dp->pdr_kept_paths[i];
+ if (kept_path == NULL)
+ continue;
+ ASSERT(kept_path[0] != '\0');
+ if ((kept = pm_name_to_dip(kept_path, 1)) == NULL)
+ continue;
+ wku_info = PM_GET_PM_INFO(kept);
+ if (wku_info == NULL) {
+ if (kept)
+ ddi_release_devi(kept);
+ continue;
+ }
+ /*
+ * Don't mess with it if it is being detached, it isn't
+ * safe to call its power entry point
+ */
+ if (wku_info->pmi_dev_pm_state & PM_DETACHING) {
+ if (kept)
+ ddi_release_devi(kept);
+ continue;
+ }
+ bring_pmdep_up(kept, 1);
+ ddi_release_devi(kept);
+ }
+ }
+}
+
+/*
+ * Bring up the 'kept' device passed as argument
+ */
+static void
+bring_pmdep_up(dev_info_t *kept_dip, int hold)
+{
+ PMD_FUNC(pmf, "bring_pmdep_up")
+ int is_all_at_normal = 0;
+
+ /*
+ * If the kept device has been unmanaged, do nothing.
+ */
+ if (!PM_GET_PM_INFO(kept_dip))
+ return;
+
+ /* Just ignore DIRECT PM device till they are released. */
+ if (!pm_processes_stopped && PM_ISDIRECT(kept_dip) &&
+ !(is_all_at_normal = pm_all_at_normal(kept_dip))) {
+ PMD(PMD_BRING, ("%s: can't bring up PM_DIRECT %s@%s(%s#%d) "
+ "controlling process did something else\n", pmf,
+ PM_DEVICE(kept_dip)))
+ DEVI(kept_dip)->devi_pm_flags |= PMC_SKIP_BRINGUP;
+ return;
+ }
+ /* if we got here the keeper had a transition from OFF->ON */
+ if (hold)
+ pm_hold_power(kept_dip);
+
+ if (!is_all_at_normal)
+ (void) pm_all_to_normal(kept_dip, PM_CANBLOCK_FAIL);
+}
+
+/*
+ * A bunch of stuff that belongs only to the next routine (or two)
+ */
+
+static const char namestr[] = "NAME=";
+static const int nameln = sizeof (namestr) - 1;
+static const char pmcompstr[] = "pm-components";
+
+struct pm_comp_pkg {
+ pm_comp_t *comp;
+ struct pm_comp_pkg *next;
+};
+
+#define isdigit(ch) ((ch) >= '0' && (ch) <= '9')
+
+#define isxdigit(ch) (isdigit(ch) || ((ch) >= 'a' && (ch) <= 'f') || \
+ ((ch) >= 'A' && (ch) <= 'F'))
+
+/*
+ * Rather than duplicate this code ...
+ * (this code excerpted from the function that follows it)
+ */
+#define FINISH_COMP { \
+ ASSERT(compp); \
+ compp->pmc_lnames_sz = size; \
+ tp = compp->pmc_lname_buf = kmem_alloc(size, KM_SLEEP); \
+ compp->pmc_numlevels = level; \
+ compp->pmc_lnames = kmem_alloc(level * sizeof (char *), KM_SLEEP); \
+ compp->pmc_lvals = kmem_alloc(level * sizeof (int), KM_SLEEP); \
+ compp->pmc_thresh = kmem_alloc(level * sizeof (int), KM_SLEEP); \
+ /* copy string out of prop array into buffer */ \
+ for (j = 0; j < level; j++) { \
+ compp->pmc_thresh[j] = INT_MAX; /* only [0] sticks */ \
+ compp->pmc_lvals[j] = lvals[j]; \
+ (void) strcpy(tp, lnames[j]); \
+ compp->pmc_lnames[j] = tp; \
+ tp += lszs[j]; \
+ } \
+ ASSERT(tp > compp->pmc_lname_buf && tp <= \
+ compp->pmc_lname_buf + compp->pmc_lnames_sz); \
+ }
+
+/*
+ * Create (empty) component data structures.
+ */
+static void
+e_pm_create_components(dev_info_t *dip, int num_components)
+{
+ struct pm_component *compp, *ocompp;
+ int i, size = 0;
+
+ ASSERT(!PM_IAM_LOCKING_DIP(dip));
+ ASSERT(!DEVI(dip)->devi_pm_components);
+ ASSERT(!(DEVI(dip)->devi_pm_flags & PMC_COMPONENTS_DONE));
+ size = sizeof (struct pm_component) * num_components;
+
+ compp = kmem_zalloc(size, KM_SLEEP);
+ ocompp = compp;
+ DEVI(dip)->devi_pm_comp_size = size;
+ DEVI(dip)->devi_pm_num_components = num_components;
+ PM_LOCK_BUSY(dip);
+ for (i = 0; i < num_components; i++) {
+ compp->pmc_timestamp = gethrestime_sec();
+ compp->pmc_norm_pwr = (uint_t)-1;
+ compp++;
+ }
+ PM_UNLOCK_BUSY(dip);
+ DEVI(dip)->devi_pm_components = ocompp;
+ DEVI(dip)->devi_pm_flags |= PMC_COMPONENTS_DONE;
+}
+
+/*
+ * Parse hex or decimal value from char string
+ */
+static char *
+pm_parsenum(char *cp, int *valp)
+{
+ int ch, offset;
+ char numbuf[256];
+ char *np = numbuf;
+ int value = 0;
+
+ ch = *cp++;
+ if (isdigit(ch)) {
+ if (ch == '0') {
+ if ((ch = *cp++) == 'x' || ch == 'X') {
+ ch = *cp++;
+ while (isxdigit(ch)) {
+ *np++ = (char)ch;
+ ch = *cp++;
+ }
+ *np = 0;
+ cp--;
+ goto hexval;
+ } else {
+ goto digit;
+ }
+ } else {
+digit:
+ while (isdigit(ch)) {
+ *np++ = (char)ch;
+ ch = *cp++;
+ }
+ *np = 0;
+ cp--;
+ goto decval;
+ }
+ } else
+ return (NULL);
+
+hexval:
+ for (np = numbuf; *np; np++) {
+ if (*np >= 'a' && *np <= 'f')
+ offset = 'a' - 10;
+ else if (*np >= 'A' && *np <= 'F')
+ offset = 'A' - 10;
+ else if (*np >= '0' && *np <= '9')
+ offset = '0';
+ value *= 16;
+ value += *np - offset;
+ }
+ *valp = value;
+ return (cp);
+
+decval:
+ offset = '0';
+ for (np = numbuf; *np; np++) {
+ value *= 10;
+ value += *np - offset;
+ }
+ *valp = value;
+ return (cp);
+}
+
+/*
+ * Set max (previously documented as "normal") power.
+ */
+static void
+e_pm_set_max_power(dev_info_t *dip, int component_number, int level)
+{
+ PM_CP(dip, component_number)->pmc_norm_pwr = level;
+}
+
+/*
+ * Internal routine for destroying components
+ * It is called even when there might not be any, so it must be forgiving.
+ */
+static void
+e_pm_destroy_components(dev_info_t *dip)
+{
+ int i;
+ struct pm_component *cp;
+
+ ASSERT(!PM_IAM_LOCKING_DIP(dip));
+ if (PM_NUMCMPTS(dip) == 0)
+ return;
+ cp = DEVI(dip)->devi_pm_components;
+ ASSERT(cp);
+ for (i = 0; i < PM_NUMCMPTS(dip); i++, cp++) {
+ int nlevels = cp->pmc_comp.pmc_numlevels;
+ kmem_free(cp->pmc_comp.pmc_lvals, nlevels * sizeof (int));
+ kmem_free(cp->pmc_comp.pmc_thresh, nlevels * sizeof (int));
+ /*
+ * For BC nodes, the rest is static in bc_comp, so skip it
+ */
+ if (PM_ISBC(dip))
+ continue;
+ kmem_free(cp->pmc_comp.pmc_name, cp->pmc_comp.pmc_name_sz);
+ kmem_free(cp->pmc_comp.pmc_lnames, nlevels * sizeof (char *));
+ kmem_free(cp->pmc_comp.pmc_lname_buf,
+ cp->pmc_comp.pmc_lnames_sz);
+ }
+ kmem_free(DEVI(dip)->devi_pm_components, DEVI(dip)->devi_pm_comp_size);
+ DEVI(dip)->devi_pm_components = NULL;
+ DEVI(dip)->devi_pm_num_components = 0;
+ DEVI(dip)->devi_pm_flags &=
+ ~(PMC_COMPONENTS_DONE | PMC_COMPONENTS_FAILED);
+}
+
+/*
+ * Read the pm-components property (if there is one) and use it to set up
+ * components. Returns a pointer to an array of component structures if
+ * pm-components found and successfully parsed, else returns NULL.
+ * Sets error return *errp to true to indicate a failure (as opposed to no
+ * property being present).
+ */
+pm_comp_t *
+pm_autoconfig(dev_info_t *dip, int *errp)
+{
+ PMD_FUNC(pmf, "autoconfig")
+ uint_t nelems;
+ char **pp;
+ pm_comp_t *compp = NULL;
+ int i, j, level, components = 0;
+ size_t size = 0;
+ struct pm_comp_pkg *p, *ptail;
+ struct pm_comp_pkg *phead = NULL;
+ int *lvals = NULL;
+ int *lszs = NULL;
+ int *np = NULL;
+ int npi = 0;
+ char **lnames = NULL;
+ char *cp, *tp;
+ pm_comp_t *ret = NULL;
+
+ ASSERT(!PM_IAM_LOCKING_DIP(dip));
+ *errp = 0; /* assume success */
+ if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+ (char *)pmcompstr, &pp, &nelems) != DDI_PROP_SUCCESS) {
+ return (NULL);
+ }
+
+ if (nelems < 3) { /* need at least one name and two levels */
+ goto errout;
+ }
+
+ /*
+ * pm_create_components is no longer allowed
+ */
+ if (PM_NUMCMPTS(dip) != 0) {
+ PMD(PMD_ERROR, ("%s: %s@%s(%s#%d) has %d comps\n",
+ pmf, PM_DEVICE(dip), PM_NUMCMPTS(dip)))
+ goto errout;
+ }
+
+ lvals = kmem_alloc(nelems * sizeof (int), KM_SLEEP);
+ lszs = kmem_alloc(nelems * sizeof (int), KM_SLEEP);
+ lnames = kmem_alloc(nelems * sizeof (char *), KM_SLEEP);
+ np = kmem_alloc(nelems * sizeof (int), KM_SLEEP);
+
+ level = 0;
+ phead = NULL;
+ for (i = 0; i < nelems; i++) {
+ cp = pp[i];
+ if (!isdigit(*cp)) { /* must be name */
+ if (strncmp(cp, namestr, nameln) != 0) {
+ goto errout;
+ }
+ if (i != 0) {
+ if (level == 0) { /* no level spec'd */
+ PMD(PMD_ERROR, ("%s: no level spec'd\n",
+ pmf))
+ goto errout;
+ }
+ np[npi++] = lvals[level - 1];
+ /* finish up previous component levels */
+ FINISH_COMP;
+ }
+ cp += nameln;
+ if (!*cp) {
+ PMD(PMD_ERROR, ("%s: nsa\n", pmf))
+ goto errout;
+ }
+ p = kmem_zalloc(sizeof (*phead), KM_SLEEP);
+ if (phead == NULL) {
+ phead = ptail = p;
+ } else {
+ ptail->next = p;
+ ptail = p;
+ }
+ compp = p->comp = kmem_zalloc(sizeof (pm_comp_t),
+ KM_SLEEP);
+ compp->pmc_name_sz = strlen(cp) + 1;
+ compp->pmc_name = kmem_zalloc(compp->pmc_name_sz,
+ KM_SLEEP);
+ (void) strncpy(compp->pmc_name, cp, compp->pmc_name_sz);
+ components++;
+ level = 0;
+ } else { /* better be power level <num>=<name> */
+#ifdef DEBUG
+ tp = cp;
+#endif
+ if (i == 0 ||
+ (cp = pm_parsenum(cp, &lvals[level])) == NULL) {
+ PMD(PMD_ERROR, ("%s: parsenum(%s)\n", pmf, tp))
+ goto errout;
+ }
+#ifdef DEBUG
+ tp = cp;
+#endif
+ if (*cp++ != '=' || !*cp) {
+ PMD(PMD_ERROR, ("%s: ex =, got %s\n", pmf, tp))
+ goto errout;
+ }
+
+ lszs[level] = strlen(cp) + 1;
+ size += lszs[level];
+ lnames[level] = cp; /* points into prop string */
+ level++;
+ }
+ }
+ np[npi++] = lvals[level - 1];
+ if (level == 0) { /* ended with a name */
+ PMD(PMD_ERROR, ("%s: ewn\n", pmf))
+ goto errout;
+ }
+ FINISH_COMP;
+
+
+ /*
+ * Now we have a list of components--we have to return instead an
+ * array of them, but we can just copy the top level and leave
+ * the rest as is
+ */
+ (void) e_pm_create_components(dip, components);
+ for (i = 0; i < components; i++)
+ e_pm_set_max_power(dip, i, np[i]);
+
+ ret = kmem_zalloc(components * sizeof (pm_comp_t), KM_SLEEP);
+ for (i = 0, p = phead; i < components; i++) {
+ ASSERT(p);
+ /*
+ * Now sanity-check values: levels must be monotonically
+ * increasing
+ */
+ if (p->comp->pmc_numlevels < 2) {
+ PMD(PMD_ERROR, ("%s: comp %s of %s@%s(%s#%d) only %d "
+ "levels\n", pmf,
+ p->comp->pmc_name, PM_DEVICE(dip),
+ p->comp->pmc_numlevels))
+ goto errout;
+ }
+ for (j = 0; j < p->comp->pmc_numlevels; j++) {
+ if ((p->comp->pmc_lvals[j] < 0) || ((j > 0) &&
+ (p->comp->pmc_lvals[j] <=
+ p->comp->pmc_lvals[j - 1]))) {
+ PMD(PMD_ERROR, ("%s: comp %s of %s@%s(%s#%d) "
+ "not mono. incr, %d follows %d\n", pmf,
+ p->comp->pmc_name, PM_DEVICE(dip),
+ p->comp->pmc_lvals[j],
+ p->comp->pmc_lvals[j - 1]))
+ goto errout;
+ }
+ }
+ ret[i] = *p->comp; /* struct assignment */
+ for (j = 0; j < i; j++) {
+ /*
+ * Test for unique component names
+ */
+ if (strcmp(ret[j].pmc_name, ret[i].pmc_name) == 0) {
+ PMD(PMD_ERROR, ("%s: %s of %s@%s(%s#%d) not "
+ "unique\n", pmf, ret[j].pmc_name,
+ PM_DEVICE(dip)))
+ goto errout;
+ }
+ }
+ ptail = p;
+ p = p->next;
+ phead = p; /* errout depends on phead making sense */
+ kmem_free(ptail->comp, sizeof (*ptail->comp));
+ kmem_free(ptail, sizeof (*ptail));
+ }
+out:
+ ddi_prop_free(pp);
+ if (lvals)
+ kmem_free(lvals, nelems * sizeof (int));
+ if (lszs)
+ kmem_free(lszs, nelems * sizeof (int));
+ if (lnames)
+ kmem_free(lnames, nelems * sizeof (char *));
+ if (np)
+ kmem_free(np, nelems * sizeof (int));
+ return (ret);
+
+errout:
+ e_pm_destroy_components(dip);
+ *errp = 1; /* signal failure */
+ cmn_err(CE_CONT, "!pm: %s property ", pmcompstr);
+ for (i = 0; i < nelems - 1; i++)
+ cmn_err(CE_CONT, "!'%s', ", pp[i]);
+ if (nelems != 0)
+ cmn_err(CE_CONT, "!'%s'", pp[nelems - 1]);
+ cmn_err(CE_CONT, "! for %s@%s(%s#%d) is ill-formed.\n", PM_DEVICE(dip));
+ for (p = phead; p; ) {
+ pm_comp_t *pp;
+ int n;
+
+ ptail = p;
+ /*
+ * Free component data structures
+ */
+ pp = p->comp;
+ n = pp->pmc_numlevels;
+ if (pp->pmc_name_sz) {
+ kmem_free(pp->pmc_name, pp->pmc_name_sz);
+ }
+ if (pp->pmc_lnames_sz) {
+ kmem_free(pp->pmc_lname_buf, pp->pmc_lnames_sz);
+ }
+ if (pp->pmc_lnames) {
+ kmem_free(pp->pmc_lnames, n * (sizeof (char *)));
+ }
+ if (pp->pmc_thresh) {
+ kmem_free(pp->pmc_thresh, n * (sizeof (int)));
+ }
+ if (pp->pmc_lvals) {
+ kmem_free(pp->pmc_lvals, n * (sizeof (int)));
+ }
+ p = ptail->next;
+ kmem_free(ptail, sizeof (*ptail));
+ }
+ if (ret != NULL)
+ kmem_free(ret, components * sizeof (pm_comp_t));
+ ret = NULL;
+ goto out;
+}
+
+/*
+ * Set threshold values for a devices components by dividing the target
+ * threshold (base) by the number of transitions and assign each transition
+ * that threshold. This will get the entire device down in the target time if
+ * all components are idle and even if there are dependencies among components.
+ *
+ * Devices may well get powered all the way down before the target time, but
+ * at least the EPA will be happy.
+ */
+void
+pm_set_device_threshold(dev_info_t *dip, int base, int flag)
+{
+ PMD_FUNC(pmf, "set_device_threshold")
+ int target_threshold = (base * 95) / 100;
+ int level, comp; /* loop counters */
+ int transitions = 0;
+ int ncomp = PM_NUMCMPTS(dip);
+ int thresh;
+ int remainder;
+ pm_comp_t *pmc;
+ int i, circ;
+
+ ASSERT(!PM_IAM_LOCKING_DIP(dip));
+ PM_LOCK_DIP(dip);
+ /*
+ * First we handle the easy one. If we're setting the default
+ * threshold for a node with children, then we set it to the
+ * default nexus threshold (currently 0) and mark it as default
+ * nexus threshold instead
+ */
+ if (PM_IS_NEXUS(dip)) {
+ if (flag == PMC_DEF_THRESH) {
+ PMD(PMD_THRESH, ("%s: [%s@%s(%s#%d) NEXDEF]\n", pmf,
+ PM_DEVICE(dip)))
+ thresh = pm_default_nexus_threshold;
+ for (comp = 0; comp < ncomp; comp++) {
+ pmc = &PM_CP(dip, comp)->pmc_comp;
+ for (level = 1; level < pmc->pmc_numlevels;
+ level++) {
+ pmc->pmc_thresh[level] = thresh;
+ }
+ }
+ DEVI(dip)->devi_pm_dev_thresh =
+ pm_default_nexus_threshold;
+ /*
+ * If the nexus node is being reconfigured back to
+ * the default threshold, adjust the notlowest count.
+ */
+ if (DEVI(dip)->devi_pm_flags &
+ (PMC_DEV_THRESH|PMC_COMP_THRESH)) {
+ PM_LOCK_POWER(dip, &circ);
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ if (PM_CURPOWER(dip, i) == 0)
+ continue;
+ mutex_enter(&pm_compcnt_lock);
+ ASSERT(pm_comps_notlowest);
+ pm_comps_notlowest--;
+ PMD(PMD_LEVEL, ("%s: %s@%s(%s#%d) decr "
+ "notlowest to %d\n", pmf,
+ PM_DEVICE(dip), pm_comps_notlowest))
+ if (pm_comps_notlowest == 0)
+ pm_ppm_notify_all_lowest(dip,
+ PM_ALL_LOWEST);
+ mutex_exit(&pm_compcnt_lock);
+ }
+ PM_UNLOCK_POWER(dip, circ);
+ }
+ DEVI(dip)->devi_pm_flags &= PMC_THRESH_NONE;
+ DEVI(dip)->devi_pm_flags |= PMC_NEXDEF_THRESH;
+ PM_UNLOCK_DIP(dip);
+ return;
+ } else if (DEVI(dip)->devi_pm_flags & PMC_NEXDEF_THRESH) {
+ /*
+ * If the nexus node is being configured for a
+ * non-default threshold, include that node in
+ * the notlowest accounting.
+ */
+ PM_LOCK_POWER(dip, &circ);
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ if (PM_CURPOWER(dip, i) == 0)
+ continue;
+ mutex_enter(&pm_compcnt_lock);
+ if (pm_comps_notlowest == 0)
+ pm_ppm_notify_all_lowest(dip,
+ PM_NOT_ALL_LOWEST);
+ pm_comps_notlowest++;
+ PMD(PMD_LEVEL, ("%s: %s@%s(%s#%d) incr "
+ "notlowest to %d\n", pmf,
+ PM_DEVICE(dip), pm_comps_notlowest))
+ mutex_exit(&pm_compcnt_lock);
+ }
+ PM_UNLOCK_POWER(dip, circ);
+ }
+ }
+ /*
+ * Compute the total number of transitions for all components
+ * of the device. Distribute the threshold evenly over them
+ */
+ for (comp = 0; comp < ncomp; comp++) {
+ pmc = &PM_CP(dip, comp)->pmc_comp;
+ ASSERT(pmc->pmc_numlevels > 1);
+ transitions += pmc->pmc_numlevels - 1;
+ }
+ ASSERT(transitions);
+ thresh = target_threshold / transitions;
+
+ for (comp = 0; comp < ncomp; comp++) {
+ pmc = &PM_CP(dip, comp)->pmc_comp;
+ for (level = 1; level < pmc->pmc_numlevels; level++) {
+ pmc->pmc_thresh[level] = thresh;
+ }
+ }
+
+#ifdef DEBUG
+ for (comp = 0; comp < ncomp; comp++) {
+ pmc = &PM_CP(dip, comp)->pmc_comp;
+ for (level = 1; level < pmc->pmc_numlevels; level++) {
+ PMD(PMD_THRESH, ("%s: thresh before %s@%s(%s#%d) "
+ "comp=%d, level=%d, %d\n", pmf, PM_DEVICE(dip),
+ comp, level, pmc->pmc_thresh[level]))
+ }
+ }
+#endif
+ /*
+ * Distribute any remainder till they are all gone
+ */
+ remainder = target_threshold - thresh * transitions;
+ level = 1;
+#ifdef DEBUG
+ PMD(PMD_THRESH, ("%s: remainder=%d target_threshold=%d thresh=%d "
+ "trans=%d\n", pmf, remainder, target_threshold, thresh,
+ transitions))
+#endif
+ while (remainder > 0) {
+ comp = 0;
+ while (remainder && (comp < ncomp)) {
+ pmc = &PM_CP(dip, comp)->pmc_comp;
+ if (level < pmc->pmc_numlevels) {
+ pmc->pmc_thresh[level] += 1;
+ remainder--;
+ }
+ comp++;
+ }
+ level++;
+ }
+#ifdef DEBUG
+ for (comp = 0; comp < ncomp; comp++) {
+ pmc = &PM_CP(dip, comp)->pmc_comp;
+ for (level = 1; level < pmc->pmc_numlevels; level++) {
+ PMD(PMD_THRESH, ("%s: thresh after %s@%s(%s#%d) "
+ "comp=%d level=%d, %d\n", pmf, PM_DEVICE(dip),
+ comp, level, pmc->pmc_thresh[level]))
+ }
+ }
+#endif
+ ASSERT(PM_IAM_LOCKING_DIP(dip));
+ DEVI(dip)->devi_pm_dev_thresh = base;
+ DEVI(dip)->devi_pm_flags &= PMC_THRESH_NONE;
+ DEVI(dip)->devi_pm_flags |= flag;
+ PM_UNLOCK_DIP(dip);
+}
+
+/*
+ * Called when there is no old-style platform power management driver
+ */
+static int
+ddi_no_platform_power(power_req_t *req)
+{
+ _NOTE(ARGUNUSED(req))
+ return (DDI_FAILURE);
+}
+
+/*
+ * This function calls the entry point supplied by the platform-specific
+ * pm driver to bring the device component 'pm_cmpt' to power level 'pm_level'.
+ * The use of global for getting the function name from platform-specific
+ * pm driver is not ideal, but it is simple and efficient.
+ * The previous property lookup was being done in the idle loop on swift
+ * systems without pmc chips and hurt deskbench performance as well as
+ * violating scheduler locking rules
+ */
+int (*pm_platform_power)(power_req_t *) = ddi_no_platform_power;
+
+/*
+ * Old obsolete interface for a device to request a power change (but only
+ * an increase in power)
+ */
+int
+ddi_dev_is_needed(dev_info_t *dip, int cmpt, int level)
+{
+ return (pm_raise_power(dip, cmpt, level));
+}
+
+/*
+ * The old obsolete interface to platform power management. Only used by
+ * Gypsy platform and APM on X86.
+ */
+int
+ddi_power(dev_info_t *dip, int pm_cmpt, int pm_level)
+{
+ power_req_t request;
+
+ request.request_type = PMR_SET_POWER;
+ request.req.set_power_req.who = dip;
+ request.req.set_power_req.cmpt = pm_cmpt;
+ request.req.set_power_req.level = pm_level;
+ return (ddi_ctlops(dip, dip, DDI_CTLOPS_POWER, &request, NULL));
+}
+
+/*
+ * A driver can invoke this from its detach routine when DDI_SUSPEND is
+ * passed. Returns true if subsequent processing could result in power being
+ * removed from the device. The arg is not currently used because it is
+ * implicit in the operation of cpr/DR.
+ */
+int
+ddi_removing_power(dev_info_t *dip)
+{
+ _NOTE(ARGUNUSED(dip))
+ return (pm_powering_down);
+}
+
+/*
+ * Returns true if a device indicates that its parent handles suspend/resume
+ * processing for it.
+ */
+int
+e_ddi_parental_suspend_resume(dev_info_t *dip)
+{
+ return (DEVI(dip)->devi_pm_flags & PMC_PARENTAL_SR);
+}
+
+/*
+ * Called for devices which indicate that their parent does suspend/resume
+ * handling for them
+ */
+int
+e_ddi_suspend(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ power_req_t request;
+ request.request_type = PMR_SUSPEND;
+ request.req.suspend_req.who = dip;
+ request.req.suspend_req.cmd = cmd;
+ return (ddi_ctlops(dip, dip, DDI_CTLOPS_POWER, &request, NULL));
+}
+
+/*
+ * Called for devices which indicate that their parent does suspend/resume
+ * handling for them
+ */
+int
+e_ddi_resume(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ power_req_t request;
+ request.request_type = PMR_RESUME;
+ request.req.resume_req.who = dip;
+ request.req.resume_req.cmd = cmd;
+ return (ddi_ctlops(dip, dip, DDI_CTLOPS_POWER, &request, NULL));
+}
+
+/*
+ * Old obsolete exported interface for drivers to create components.
+ * This is now handled by exporting the pm-components property.
+ */
+int
+pm_create_components(dev_info_t *dip, int num_components)
+{
+ PMD_FUNC(pmf, "pm_create_components")
+
+ if (num_components < 1)
+ return (DDI_FAILURE);
+
+ if (!DEVI_IS_ATTACHING(dip)) {
+ return (DDI_FAILURE);
+ }
+
+ /* don't need to lock dip because attach is single threaded */
+ if (DEVI(dip)->devi_pm_components) {
+ PMD(PMD_ERROR, ("%s: %s@%s(%s#%d) already has %d\n", pmf,
+ PM_DEVICE(dip), PM_NUMCMPTS(dip)))
+ return (DDI_FAILURE);
+ }
+ e_pm_create_components(dip, num_components);
+ DEVI(dip)->devi_pm_flags |= PMC_BC;
+ e_pm_default_components(dip, num_components);
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Obsolete interface previously called by drivers to destroy their components
+ * at detach time. This is now done automatically. However, we need to keep
+ * this for the old drivers.
+ */
+void
+pm_destroy_components(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "pm_destroy_components")
+ dev_info_t *pdip = ddi_get_parent(dip);
+
+ PMD(PMD_REMDEV | PMD_KIDSUP, ("%s: %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+ ASSERT(DEVI_IS_DETACHING(dip));
+#ifdef DEBUG
+ if (!PM_ISBC(dip))
+ cmn_err(CE_WARN, "!driver exporting pm-components property "
+ "(%s@%s) calls pm_destroy_components", PM_NAME(dip),
+ PM_ADDR(dip));
+#endif
+ /*
+ * We ignore this unless this is an old-style driver, except for
+ * printing the message above
+ */
+ if (PM_NUMCMPTS(dip) == 0 || !PM_ISBC(dip)) {
+ PMD(PMD_REMDEV, ("%s: ignore %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+ return;
+ }
+ ASSERT(PM_GET_PM_INFO(dip));
+
+ /*
+ * pm_unmanage will clear info pointer later, after dealing with
+ * dependencies
+ */
+ ASSERT(!PM_GET_PM_SCAN(dip)); /* better be gone already */
+ /*
+ * Now adjust parent's kidsupcnt. We check only comp 0.
+ * Parents that get notification are not adjusted because their
+ * kidsupcnt is always 0 (or 1 during probe and attach).
+ */
+ if ((PM_CURPOWER(dip, 0) != 0) && pdip && !PM_WANTS_NOTIFICATION(pdip))
+ pm_rele_power(pdip);
+#ifdef DEBUG
+ else {
+ PMD(PMD_KIDSUP, ("%s: kuc stays %s@%s(%s#%d) comps gone\n",
+ pmf, PM_DEVICE(dip)))
+ }
+#endif
+ e_pm_destroy_components(dip);
+ /*
+ * Forget we ever knew anything about the components of this device
+ */
+ DEVI(dip)->devi_pm_flags &=
+ ~(PMC_BC | PMC_COMPONENTS_DONE | PMC_COMPONENTS_FAILED);
+}
+
+/*
+ * Exported interface for a driver to set a component busy.
+ */
+int
+pm_busy_component(dev_info_t *dip, int cmpt)
+{
+ struct pm_component *cp;
+
+ ASSERT(dip != NULL);
+ if (!e_pm_valid_info(dip, NULL) || !e_pm_valid_comp(dip, cmpt, &cp))
+ return (DDI_FAILURE);
+ PM_LOCK_BUSY(dip);
+ cp->pmc_busycount++;
+ cp->pmc_timestamp = 0;
+ PM_UNLOCK_BUSY(dip);
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Exported interface for a driver to set a component idle.
+ */
+int
+pm_idle_component(dev_info_t *dip, int cmpt)
+{
+ PMD_FUNC(pmf, "pm_idle_component")
+ struct pm_component *cp;
+ pm_scan_t *scanp = PM_GET_PM_SCAN(dip);
+
+ if (!e_pm_valid_info(dip, NULL) || !e_pm_valid_comp(dip, cmpt, &cp))
+ return (DDI_FAILURE);
+
+ PM_LOCK_BUSY(dip);
+ if (cp->pmc_busycount) {
+ if (--(cp->pmc_busycount) == 0)
+ cp->pmc_timestamp = gethrestime_sec();
+ } else {
+ cp->pmc_timestamp = gethrestime_sec();
+ }
+
+ PM_UNLOCK_BUSY(dip);
+
+ /*
+ * if device becomes idle during idle down period, try scan it down
+ */
+ if (scanp && PM_IS_PID(dip)) {
+ PMD(PMD_IDLEDOWN, ("%s: %s@%s(%s#%d) idle.\n", pmf,
+ PM_DEVICE(dip)))
+ pm_rescan(dip);
+ return (DDI_SUCCESS);
+ }
+
+ /*
+ * handle scan not running with nexus threshold == 0
+ */
+
+ if (PM_IS_NEXUS(dip) && (cp->pmc_busycount == 0)) {
+ pm_rescan(dip);
+ }
+
+ return (DDI_SUCCESS);
+}
+
+/*
+ * This is the old obsolete interface called by drivers to set their normal
+ * power. Thus we can't fix its behavior or return a value.
+ * This functionality is replaced by the pm-component property.
+ * We'll only get components destroyed while no power management is
+ * going on (and the device is detached), so we don't need a mutex here
+ */
+void
+pm_set_normal_power(dev_info_t *dip, int comp, int level)
+{
+ PMD_FUNC(pmf, "set_normal_power")
+#ifdef DEBUG
+ if (!PM_ISBC(dip))
+ cmn_err(CE_WARN, "!call to pm_set_normal_power() by %s@%s "
+ "(driver exporting pm-components property) ignored",
+ PM_NAME(dip), PM_ADDR(dip));
+#endif
+ if (PM_ISBC(dip)) {
+ PMD(PMD_NORM, ("%s: %s@%s(%s#%d) set normal power comp=%d, "
+ "level=%d\n", pmf, PM_DEVICE(dip), comp, level))
+ e_pm_set_max_power(dip, comp, level);
+ e_pm_default_levels(dip, PM_CP(dip, comp), level);
+ }
+}
+
+/*
+ * Called on a successfully detached driver to free pm resources
+ */
+static void
+pm_stop(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "stop")
+ dev_info_t *pdip = ddi_get_parent(dip);
+
+ ASSERT(!PM_IAM_LOCKING_DIP(dip));
+ /* stopping scan, destroy scan data structure */
+ if (!PM_ISBC(dip)) {
+ pm_scan_stop(dip);
+ pm_scan_fini(dip);
+ }
+
+ if (PM_GET_PM_INFO(dip) != NULL) {
+ if (pm_unmanage(dip) == DDI_SUCCESS) {
+ /*
+ * Old style driver may have called
+ * pm_destroy_components already, but just in case ...
+ */
+ e_pm_destroy_components(dip);
+ } else {
+ PMD(PMD_FAIL, ("%s: can't pm_unmanage %s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(dip)))
+ }
+ } else {
+ if (PM_NUMCMPTS(dip))
+ e_pm_destroy_components(dip);
+ else {
+ if (DEVI(dip)->devi_pm_flags & PMC_NOPMKID) {
+ DEVI(dip)->devi_pm_flags &= ~PMC_NOPMKID;
+ if (pdip && !PM_WANTS_NOTIFICATION(pdip)) {
+ pm_rele_power(pdip);
+ } else if (pdip && MDI_VHCI(pdip)) {
+ (void) mdi_power(pdip,
+ MDI_PM_RELE_POWER,
+ (void *)dip, NULL, 0);
+ }
+ }
+ }
+ }
+}
+
+/*
+ * The node is the subject of a reparse pm props ioctl. Throw away the old
+ * info and start over.
+ */
+int
+e_new_pm_props(dev_info_t *dip)
+{
+ if (PM_GET_PM_INFO(dip) != NULL) {
+ pm_stop(dip);
+
+ if (e_pm_manage(dip, PM_STYLE_NEW) != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+ }
+ e_pm_props(dip);
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Device has been attached, so process its pm properties
+ */
+void
+e_pm_props(dev_info_t *dip)
+{
+ char *pp;
+ int len;
+ int flags = 0;
+ int propflag = DDI_PROP_DONTPASS|DDI_PROP_CANSLEEP;
+
+ /*
+ * It doesn't matter if we do this more than once, we should always
+ * get the same answers, and if not, then the last one in is the
+ * best one.
+ */
+ if (ddi_getlongprop(DDI_DEV_T_ANY, dip, propflag, "pm-hardware-state",
+ (caddr_t)&pp, &len) == DDI_PROP_SUCCESS) {
+ if (strcmp(pp, "needs-suspend-resume") == 0) {
+ flags = PMC_NEEDS_SR;
+ } else if (strcmp(pp, "no-suspend-resume") == 0) {
+ flags = PMC_NO_SR;
+ } else if (strcmp(pp, "parental-suspend-resume") == 0) {
+ flags = PMC_PARENTAL_SR;
+ } else {
+ cmn_err(CE_NOTE, "!device %s@%s has unrecognized "
+ "%s property value '%s'", PM_NAME(dip),
+ PM_ADDR(dip), "pm-hardware-state", pp);
+ }
+ kmem_free(pp, len);
+ }
+ /*
+ * This next segment (PMC_WANTS_NOTIFY) is in
+ * support of nexus drivers which will want to be involved in
+ * (or at least notified of) their child node's power level transitions.
+ * "pm-want-child-notification?" is defined by the parent.
+ */
+ if (ddi_prop_exists(DDI_DEV_T_ANY, dip, propflag,
+ "pm-want-child-notification?") && PM_HAS_BUS_POWER(dip))
+ flags |= PMC_WANTS_NOTIFY;
+ ASSERT(PM_HAS_BUS_POWER(dip) || !ddi_prop_exists(DDI_DEV_T_ANY,
+ dip, propflag, "pm-want-child-notification?"));
+ if (ddi_prop_exists(DDI_DEV_T_ANY, dip, propflag,
+ "no-involuntary-power-cycles"))
+ flags |= PMC_NO_INVOL;
+ /* devfs single threads us */
+ DEVI(dip)->devi_pm_flags |= flags;
+}
+
+/*
+ * This is the DDI_CTLOPS_POWER handler that is used when there is no ppm
+ * driver which has claimed a node.
+ * Sets old_power in arg struct.
+ */
+static int
+pm_default_ctlops(dev_info_t *dip, dev_info_t *rdip,
+ ddi_ctl_enum_t ctlop, void *arg, void *result)
+{
+ _NOTE(ARGUNUSED(dip))
+ PMD_FUNC(pmf, "ctlops")
+ power_req_t *reqp = (power_req_t *)arg;
+ int retval;
+ dev_info_t *target_dip;
+ int new_level, old_level, cmpt;
+#ifdef DEBUG
+ char *format;
+#endif
+
+ /*
+ * The interface for doing the actual power level changes is now
+ * through the DDI_CTLOPS_POWER bus_ctl, so that we can plug in
+ * different platform-specific power control drivers.
+ *
+ * This driver implements the "default" version of this interface.
+ * If no ppm driver has been installed then this interface is called
+ * instead.
+ */
+ ASSERT(dip == NULL);
+ switch (ctlop) {
+ case DDI_CTLOPS_POWER:
+ switch (reqp->request_type) {
+ case PMR_PPM_SET_POWER:
+ {
+ target_dip = reqp->req.ppm_set_power_req.who;
+ ASSERT(target_dip == rdip);
+ new_level = reqp->req.ppm_set_power_req.new_level;
+ cmpt = reqp->req.ppm_set_power_req.cmpt;
+ /* pass back old power for the PM_LEVEL_UNKNOWN case */
+ old_level = PM_CURPOWER(target_dip, cmpt);
+ reqp->req.ppm_set_power_req.old_level = old_level;
+ retval = pm_power(target_dip, cmpt, new_level);
+ PMD(PMD_PPM, ("%s: PPM_SET_POWER %s@%s(%s#%d)[%d] %d->"
+ "%d %s\n", pmf, PM_DEVICE(target_dip), cmpt,
+ old_level, new_level, (retval == DDI_SUCCESS ?
+ "chd" : "no chg")))
+ return (retval);
+ }
+
+ case PMR_PPM_PRE_DETACH:
+ case PMR_PPM_POST_DETACH:
+ case PMR_PPM_PRE_ATTACH:
+ case PMR_PPM_POST_ATTACH:
+ case PMR_PPM_PRE_PROBE:
+ case PMR_PPM_POST_PROBE:
+ case PMR_PPM_PRE_RESUME:
+ case PMR_PPM_INIT_CHILD:
+ case PMR_PPM_UNINIT_CHILD:
+#ifdef DEBUG
+ switch (reqp->request_type) {
+ case PMR_PPM_PRE_DETACH:
+ format = "%s: PMR_PPM_PRE_DETACH "
+ "%s@%s(%s#%d)\n";
+ break;
+ case PMR_PPM_POST_DETACH:
+ format = "%s: PMR_PPM_POST_DETACH "
+ "%s@%s(%s#%d) rets %d\n";
+ break;
+ case PMR_PPM_PRE_ATTACH:
+ format = "%s: PMR_PPM_PRE_ATTACH "
+ "%s@%s(%s#%d)\n";
+ break;
+ case PMR_PPM_POST_ATTACH:
+ format = "%s: PMR_PPM_POST_ATTACH "
+ "%s@%s(%s#%d) rets %d\n";
+ break;
+ case PMR_PPM_PRE_PROBE:
+ format = "%s: PMR_PPM_PRE_PROBE "
+ "%s@%s(%s#%d)\n";
+ break;
+ case PMR_PPM_POST_PROBE:
+ format = "%s: PMR_PPM_POST_PROBE "
+ "%s@%s(%s#%d) rets %d\n";
+ break;
+ case PMR_PPM_PRE_RESUME:
+ format = "%s: PMR_PPM_PRE_RESUME "
+ "%s@%s(%s#%d) rets %d\n";
+ break;
+ case PMR_PPM_INIT_CHILD:
+ format = "%s: PMR_PPM_INIT_CHILD "
+ "%s@%s(%s#%d)\n";
+ break;
+ case PMR_PPM_UNINIT_CHILD:
+ format = "%s: PMR_PPM_UNINIT_CHILD "
+ "%s@%s(%s#%d)\n";
+ break;
+ default:
+ break;
+ }
+ PMD(PMD_PPM, (format, pmf, PM_DEVICE(rdip),
+ reqp->req.ppm_config_req.result))
+#endif
+ return (DDI_SUCCESS);
+
+ case PMR_PPM_POWER_CHANGE_NOTIFY:
+ /*
+ * Nothing for us to do
+ */
+ ASSERT(reqp->req.ppm_notify_level_req.who == rdip);
+ PMD(PMD_PPM, ("%s: PMR_PPM_POWER_CHANGE_NOTIFY "
+ "%s@%s(%s#%d)[%d] %d->%d\n", pmf,
+ PM_DEVICE(reqp->req.ppm_notify_level_req.who),
+ reqp->req.ppm_notify_level_req.cmpt,
+ PM_CURPOWER(reqp->req.ppm_notify_level_req.who,
+ reqp->req.ppm_notify_level_req.cmpt),
+ reqp->req.ppm_notify_level_req.new_level))
+ return (DDI_SUCCESS);
+
+ case PMR_PPM_UNMANAGE:
+ PMD(PMD_PPM, ("%s: PMR_PPM_UNMANAGE %s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(rdip)))
+ return (DDI_SUCCESS);
+
+ case PMR_PPM_LOCK_POWER:
+ pm_lock_power_single(reqp->req.ppm_lock_power_req.who,
+ reqp->req.ppm_lock_power_req.circp);
+ return (DDI_SUCCESS);
+
+ case PMR_PPM_UNLOCK_POWER:
+ pm_unlock_power_single(
+ reqp->req.ppm_unlock_power_req.who,
+ reqp->req.ppm_unlock_power_req.circ);
+ return (DDI_SUCCESS);
+
+ case PMR_PPM_TRY_LOCK_POWER:
+ *(int *)result = pm_try_locking_power_single(
+ reqp->req.ppm_lock_power_req.who,
+ reqp->req.ppm_lock_power_req.circp);
+ return (DDI_SUCCESS);
+
+ case PMR_PPM_POWER_LOCK_OWNER:
+ target_dip = reqp->req.ppm_power_lock_owner_req.who;
+ ASSERT(target_dip == rdip);
+ reqp->req.ppm_power_lock_owner_req.owner =
+ DEVI(rdip)->devi_busy_thread;
+ return (DDI_SUCCESS);
+ default:
+ PMD(PMD_ERROR, ("%s: default!\n", pmf))
+ return (DDI_FAILURE);
+ }
+
+ default:
+ PMD(PMD_ERROR, ("%s: unknown\n", pmf))
+ return (DDI_FAILURE);
+ }
+}
+
+/*
+ * We overload the bus_ctl ops here--perhaps we ought to have a distinct
+ * power_ops struct for this functionality instead?
+ * However, we only ever do this on a ppm driver.
+ */
+int
+pm_ctlops(dev_info_t *d, dev_info_t *r, ddi_ctl_enum_t op, void *a, void *v)
+{
+ int (*fp)();
+
+ /* if no ppm handler, call the default routine */
+ if (d == NULL) {
+ return (pm_default_ctlops(d, r, op, a, v));
+ }
+ if (!d || !r)
+ return (DDI_FAILURE);
+ ASSERT(DEVI(d)->devi_ops && DEVI(d)->devi_ops->devo_bus_ops &&
+ DEVI(d)->devi_ops->devo_bus_ops->bus_ctl);
+
+ fp = DEVI(d)->devi_ops->devo_bus_ops->bus_ctl;
+ return ((*fp)(d, r, op, a, v));
+}
+
+/*
+ * Called on a node when attach completes or the driver makes its first pm
+ * call (whichever comes first).
+ * In the attach case, device may not be power manageable at all.
+ * Don't need to lock the dip because we're single threaded by the devfs code
+ */
+static int
+pm_start(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "start")
+ int ret;
+ dev_info_t *pdip = ddi_get_parent(dip);
+ int e_pm_manage(dev_info_t *, int);
+ void pm_noinvol_specd(dev_info_t *dip);
+
+ e_pm_props(dip);
+ pm_noinvol_specd(dip);
+ /*
+ * If this dip has already been processed, don't mess with it
+ * (but decrement the speculative count we did above, as whatever
+ * code put it under pm already will have dealt with it)
+ */
+ if (PM_GET_PM_INFO(dip)) {
+ PMD(PMD_KIDSUP, ("%s: pm already done for %s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(dip)))
+ return (0);
+ }
+ ret = e_pm_manage(dip, PM_STYLE_UNKNOWN);
+
+ if (PM_GET_PM_INFO(dip) == NULL) {
+ /*
+ * keep the kidsupcount increment as is
+ */
+ DEVI(dip)->devi_pm_flags |= PMC_NOPMKID;
+ if (pdip && !PM_WANTS_NOTIFICATION(pdip)) {
+ pm_hold_power(pdip);
+ } else if (pdip && MDI_VHCI(pdip)) {
+ (void) mdi_power(pdip, MDI_PM_HOLD_POWER,
+ (void *)dip, NULL, 0);
+ }
+
+ PMD(PMD_KIDSUP, ("%s: pm of %s@%s(%s#%d) failed, parent "
+ "left up\n", pmf, PM_DEVICE(dip)))
+ }
+
+ return (ret);
+}
+
+/*
+ * Keep a list of recorded thresholds. For now we just keep a list and
+ * search it linearly. We don't expect too many entries. Can always hash it
+ * later if we need to.
+ */
+void
+pm_record_thresh(pm_thresh_rec_t *rp)
+{
+ pm_thresh_rec_t *pptr, *ptr;
+
+ ASSERT(*rp->ptr_physpath);
+ rw_enter(&pm_thresh_rwlock, RW_WRITER);
+ for (pptr = NULL, ptr = pm_thresh_head;
+ ptr; pptr = ptr, ptr = ptr->ptr_next) {
+ if (strcmp(rp->ptr_physpath, ptr->ptr_physpath) == 0) {
+ /* replace this one */
+ rp->ptr_next = ptr->ptr_next;
+ if (pptr) {
+ pptr->ptr_next = rp;
+ } else {
+ pm_thresh_head = rp;
+ }
+ rw_exit(&pm_thresh_rwlock);
+ kmem_free(ptr, ptr->ptr_size);
+ return;
+ }
+ continue;
+ }
+ /*
+ * There was not a match in the list, insert this one in front
+ */
+ if (pm_thresh_head) {
+ rp->ptr_next = pm_thresh_head;
+ pm_thresh_head = rp;
+ } else {
+ rp->ptr_next = NULL;
+ pm_thresh_head = rp;
+ }
+ rw_exit(&pm_thresh_rwlock);
+}
+
+/*
+ * Create a new dependency record and hang a new dependency entry off of it
+ */
+pm_pdr_t *
+newpdr(char *kept, char *keeps, int isprop)
+{
+ size_t size = strlen(kept) + strlen(keeps) + 2 + sizeof (pm_pdr_t);
+ pm_pdr_t *p = kmem_zalloc(size, KM_SLEEP);
+ p->pdr_size = size;
+ p->pdr_isprop = isprop;
+ p->pdr_kept_paths = NULL;
+ p->pdr_kept_count = 0;
+ p->pdr_kept = (char *)((intptr_t)p + sizeof (pm_pdr_t));
+ (void) strcpy(p->pdr_kept, kept);
+ p->pdr_keeper = (char *)((intptr_t)p->pdr_kept + strlen(kept) + 1);
+ (void) strcpy(p->pdr_keeper, keeps);
+ ASSERT((intptr_t)p->pdr_keeper + strlen(p->pdr_keeper) + 1 <=
+ (intptr_t)p + size);
+ ASSERT((intptr_t)p->pdr_kept + strlen(p->pdr_kept) + 1 <=
+ (intptr_t)p + size);
+ return (p);
+}
+
+/*
+ * Keep a list of recorded dependencies. We only keep the
+ * keeper -> kept list for simplification. At this point We do not
+ * care about whether the devices are attached or not yet,
+ * this would be done in pm_keeper() and pm_kept().
+ * If a PM_RESET_PM happens, then we tear down and forget the dependencies,
+ * and it is up to the user to issue the ioctl again if they want it
+ * (e.g. pmconfig)
+ * Returns true if dependency already exists in the list.
+ */
+int
+pm_record_keeper(char *kept, char *keeper, int isprop)
+{
+ PMD_FUNC(pmf, "record_keeper")
+ pm_pdr_t *npdr, *ppdr, *pdr;
+
+ PMD(PMD_KEEPS, ("%s: %s, %s\n", pmf, kept, keeper))
+ ASSERT(kept && keeper);
+#ifdef DEBUG
+ if (pm_debug & PMD_KEEPS)
+ prdeps("pm_record_keeper entry");
+#endif
+ for (ppdr = NULL, pdr = pm_dep_head; pdr;
+ ppdr = pdr, pdr = pdr->pdr_next) {
+ PMD(PMD_KEEPS, ("%s: check %s, %s\n", pmf, pdr->pdr_kept,
+ pdr->pdr_keeper))
+ if (strcmp(kept, pdr->pdr_kept) == 0 &&
+ strcmp(keeper, pdr->pdr_keeper) == 0) {
+ PMD(PMD_KEEPS, ("%s: match\n", pmf))
+ return (1);
+ }
+ }
+ /*
+ * We did not find any match, so we have to make an entry
+ */
+ npdr = newpdr(kept, keeper, isprop);
+ if (ppdr) {
+ ASSERT(ppdr->pdr_next == NULL);
+ ppdr->pdr_next = npdr;
+ } else {
+ ASSERT(pm_dep_head == NULL);
+ pm_dep_head = npdr;
+ }
+#ifdef DEBUG
+ if (pm_debug & PMD_KEEPS)
+ prdeps("pm_record_keeper after new record");
+#endif
+ if (!isprop)
+ pm_unresolved_deps++;
+ else
+ pm_prop_deps++;
+ return (0);
+}
+
+/*
+ * Look up this device in the set of devices we've seen ioctls for
+ * to see if we are holding a threshold spec for it. If so, make it so.
+ * At ioctl time, we were given the physical path of the device.
+ */
+int
+pm_thresh_specd(dev_info_t *dip)
+{
+ void pm_apply_recorded_thresh(dev_info_t *, pm_thresh_rec_t *);
+ char *path = 0;
+ char pathbuf[MAXNAMELEN];
+ pm_thresh_rec_t *rp;
+
+ path = ddi_pathname(dip, pathbuf);
+
+ rw_enter(&pm_thresh_rwlock, RW_READER);
+ for (rp = pm_thresh_head; rp; rp = rp->ptr_next) {
+ if (strcmp(rp->ptr_physpath, path) != 0)
+ continue;
+ pm_apply_recorded_thresh(dip, rp);
+ rw_exit(&pm_thresh_rwlock);
+ return (1);
+ }
+ rw_exit(&pm_thresh_rwlock);
+ return (0);
+}
+
+static int
+pm_set_keeping(dev_info_t *keeper, dev_info_t *kept)
+{
+ PMD_FUNC(pmf, "set_keeping")
+ pm_info_t *kept_info;
+ int j, up = 0, circ;
+ void prdeps(char *);
+
+ PMD(PMD_KEEPS, ("%s: keeper=%s@%s(%s#%d), kept=%s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(keeper), PM_DEVICE(kept)))
+#ifdef DEBUG
+ if (pm_debug & PMD_KEEPS)
+ prdeps("Before PAD\n");
+#endif
+ ASSERT(keeper != kept);
+ if (PM_GET_PM_INFO(keeper) == NULL) {
+ cmn_err(CE_CONT, "!device %s@%s(%s#%d) keeps up device "
+ "%s@%s(%s#%d), but the latter is not power managed",
+ PM_DEVICE(keeper), PM_DEVICE(kept));
+ PMD((PMD_FAIL | PMD_KEEPS), ("%s: keeper %s@%s(%s#%d) is not"
+ "power managed\n", pmf, PM_DEVICE(keeper)))
+ return (0);
+ }
+ kept_info = PM_GET_PM_INFO(kept);
+ ASSERT(kept_info);
+ PM_LOCK_POWER(keeper, &circ);
+ for (j = 0; j < PM_NUMCMPTS(keeper); j++) {
+ if (PM_CURPOWER(keeper, j)) {
+ up++;
+ break;
+ }
+ }
+ if (up) {
+ /* Bringup and maintain a hold on the kept */
+ PMD(PMD_KEEPS, ("%s: place a hold on kept %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(kept)))
+ bring_pmdep_up(kept, 1);
+ }
+ PM_UNLOCK_POWER(keeper, circ);
+#ifdef DEBUG
+ if (pm_debug & PMD_KEEPS)
+ prdeps("After PAD\n");
+#endif
+ return (1);
+}
+
+/*
+ * Should this device keep up another device?
+ * Look up this device in the set of devices we've seen ioctls for
+ * to see if we are holding a dependency spec for it. If so, make it so.
+ * Because we require the kept device to be attached already in order to
+ * make the list entry (and hold it), we only need to look for keepers.
+ * At ioctl time, we were given the physical path of the device.
+ */
+int
+pm_keeper(char *keeper)
+{
+ PMD_FUNC(pmf, "keeper")
+ int pm_apply_recorded_dep(dev_info_t *, pm_pdr_t *);
+ dev_info_t *dip;
+ pm_pdr_t *dp;
+ dev_info_t *kept = NULL;
+ int ret = 0;
+ int i;
+
+ if (!pm_unresolved_deps && !pm_prop_deps)
+ return (0);
+ ASSERT(keeper != NULL);
+ dip = pm_name_to_dip(keeper, 1);
+ if (dip == NULL)
+ return (0);
+ PMD(PMD_KEEPS, ("%s: keeper=%s\n", pmf, keeper))
+ for (dp = pm_dep_head; dp; dp = dp->pdr_next) {
+ if (!dp->pdr_isprop) {
+ if (!pm_unresolved_deps)
+ continue;
+ PMD(PMD_KEEPS, ("%s: keeper %s\n", pmf, dp->pdr_keeper))
+ if (dp->pdr_satisfied) {
+ PMD(PMD_KEEPS, ("%s: satisfied\n", pmf))
+ continue;
+ }
+ if (strcmp(dp->pdr_keeper, keeper) == 0) {
+ ret += pm_apply_recorded_dep(dip, dp);
+ }
+ } else {
+ if (strcmp(dp->pdr_keeper, keeper) != 0)
+ continue;
+ for (i = 0; i < dp->pdr_kept_count; i++) {
+ if (dp->pdr_kept_paths[i] == NULL)
+ continue;
+ kept = pm_name_to_dip(dp->pdr_kept_paths[i], 1);
+ if (kept == NULL)
+ continue;
+ ASSERT(ddi_prop_exists(DDI_DEV_T_ANY, kept,
+ DDI_PROP_DONTPASS, dp->pdr_kept));
+ PMD(PMD_KEEPS, ("%s: keeper=%s@%s(%s#%d), "
+ "kept=%s@%s(%s#%d) keptcnt=%d\n",
+ pmf, PM_DEVICE(dip), PM_DEVICE(kept),
+ dp->pdr_kept_count))
+ if (kept != dip) {
+ ret += pm_set_keeping(dip, kept);
+ }
+ ddi_release_devi(kept);
+ }
+
+ }
+ }
+ ddi_release_devi(dip);
+ return (ret);
+}
+
+/*
+ * Should this device be kept up by another device?
+ * Look up all dependency recorded from PM_ADD_DEPENDENT and
+ * PM_ADD_DEPENDENT_PROPERTY ioctls. Record down on the keeper's
+ * kept device lists.
+ */
+static int
+pm_kept(char *keptp)
+{
+ PMD_FUNC(pmf, "kept")
+ pm_pdr_t *dp;
+ int found = 0;
+ int ret = 0;
+ dev_info_t *keeper;
+ dev_info_t *kept;
+ size_t length;
+ int i;
+ char **paths;
+ char *path;
+
+ ASSERT(keptp != NULL);
+ kept = pm_name_to_dip(keptp, 1);
+ if (kept == NULL)
+ return (0);
+ PMD(PMD_KEEPS, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(kept)))
+ for (dp = pm_dep_head; dp; dp = dp->pdr_next) {
+ if (dp->pdr_isprop) {
+ PMD(PMD_KEEPS, ("%s: property %s\n", pmf, dp->pdr_kept))
+ if (ddi_prop_exists(DDI_DEV_T_ANY, kept,
+ DDI_PROP_DONTPASS, dp->pdr_kept)) {
+ /*
+ * Dont allow self dependency.
+ */
+ if (strcmp(dp->pdr_keeper, keptp) == 0)
+ continue;
+ keeper = pm_name_to_dip(dp->pdr_keeper, 1);
+ if (keeper == NULL)
+ continue;
+ PMD(PMD_KEEPS, ("%s: adding to kepts path list "
+ "%p\n", pmf, (void *)kept))
+#ifdef DEBUG
+ if (pm_debug & PMD_DEP)
+ prdeps("Before Adding from pm_kept\n");
+#endif
+ /*
+ * Add ourselves to the dip list.
+ */
+ if (dp->pdr_kept_count == 0) {
+ length = strlen(keptp) + 1;
+ path =
+ kmem_alloc(length, KM_SLEEP);
+ paths = kmem_alloc(sizeof (char **),
+ KM_SLEEP);
+ (void) strcpy(path, keptp);
+ paths[0] = path;
+ dp->pdr_kept_paths = paths;
+ dp->pdr_kept_count++;
+ } else {
+ /* Check to see if already on list */
+ for (i = 0; i < dp->pdr_kept_count;
+ i++) {
+ if (strcmp(keptp,
+ dp->pdr_kept_paths[i])
+ == 0) {
+ found++;
+ break;
+ }
+ }
+ if (found) {
+ ddi_release_devi(keeper);
+ continue;
+ }
+ length = dp->pdr_kept_count *
+ sizeof (char **);
+ paths = kmem_alloc(
+ length + sizeof (char **),
+ KM_SLEEP);
+ if (dp->pdr_kept_count) {
+ bcopy(dp->pdr_kept_paths,
+ paths, length);
+ kmem_free(dp->pdr_kept_paths,
+ length);
+ }
+ dp->pdr_kept_paths = paths;
+ length = strlen(keptp) + 1;
+ path =
+ kmem_alloc(length, KM_SLEEP);
+ (void) strcpy(path, keptp);
+ dp->pdr_kept_paths[i] = path;
+ dp->pdr_kept_count++;
+ }
+#ifdef DEBUG
+ if (pm_debug & PMD_DEP)
+ prdeps("After from pm_kept\n");
+#endif
+ if (keeper) {
+ ret += pm_set_keeping(keeper, kept);
+ ddi_release_devi(keeper);
+ }
+ }
+ } else {
+ /*
+ * pm_keeper would be called later to do
+ * the actual pm_set_keeping.
+ */
+ PMD(PMD_KEEPS, ("%s: adding to kepts path list %p\n",
+ pmf, (void *)kept))
+#ifdef DEBUG
+ if (pm_debug & PMD_DEP)
+ prdeps("Before Adding from pm_kept\n");
+#endif
+ if (strcmp(keptp, dp->pdr_kept) == 0) {
+ if (dp->pdr_kept_paths == NULL) {
+ length = strlen(keptp) + 1;
+ path =
+ kmem_alloc(length, KM_SLEEP);
+ paths = kmem_alloc(sizeof (char **),
+ KM_SLEEP);
+ (void) strcpy(path, keptp);
+ paths[0] = path;
+ dp->pdr_kept_paths = paths;
+ dp->pdr_kept_count++;
+ }
+ }
+#ifdef DEBUG
+ if (pm_debug & PMD_DEP)
+ prdeps("After from pm_kept\n");
+#endif
+ }
+ }
+ ddi_release_devi(kept);
+ return (ret);
+}
+
+/*
+ * Apply a recorded dependency. dp specifies the dependency, and
+ * keeper is already known to be the device that keeps up the other (kept) one.
+ * We have to the whole tree for the "kept" device, then apply
+ * the dependency (which may already be applied).
+ */
+int
+pm_apply_recorded_dep(dev_info_t *keeper, pm_pdr_t *dp)
+{
+ PMD_FUNC(pmf, "apply_recorded_dep")
+ dev_info_t *kept = NULL;
+ int ret = 0;
+ char *keptp = NULL;
+
+ /*
+ * Device to Device dependency can only be 1 to 1.
+ */
+ if (dp->pdr_kept_paths == NULL)
+ return (0);
+ keptp = dp->pdr_kept_paths[0];
+ if (keptp == NULL)
+ return (0);
+ ASSERT(*keptp != '\0');
+ kept = pm_name_to_dip(keptp, 1);
+ if (kept == NULL)
+ return (0);
+ if (kept) {
+ PMD(PMD_KEEPS, ("%s: keeper=%s, kept=%s\n", pmf,
+ dp->pdr_keeper, keptp))
+ if (pm_set_keeping(keeper, kept)) {
+ ASSERT(dp->pdr_satisfied == 0);
+ dp->pdr_satisfied = 1;
+ ASSERT(pm_unresolved_deps);
+ pm_unresolved_deps--;
+ ret++;
+ }
+ }
+ ddi_release_devi(kept);
+
+ return (ret);
+}
+
+/*
+ * Called from common/io/pm.c
+ */
+int
+pm_cur_power(pm_component_t *cp)
+{
+ return (cur_power(cp));
+}
+
+/*
+ * External interface to sanity-check a power level.
+ */
+int
+pm_valid_power(dev_info_t *dip, int comp, int level)
+{
+ PMD_FUNC(pmf, "valid_power")
+
+ if (comp >= 0 && comp < PM_NUMCMPTS(dip) && level >= 0)
+ return (e_pm_valid_power(dip, comp, level));
+ else {
+ PMD(PMD_FAIL, ("%s: comp=%d, ncomp=%d, level=%d\n",
+ pmf, comp, PM_NUMCMPTS(dip), level))
+ return (0);
+ }
+}
+
+/*
+ * Called when a device that is direct power managed needs to change state.
+ * This routine arranges to block the request until the process managing
+ * the device makes the change (or some other incompatible change) or
+ * the process closes /dev/pm.
+ */
+static int
+pm_block(dev_info_t *dip, int comp, int newpower, int oldpower)
+{
+ pm_rsvp_t *new = kmem_zalloc(sizeof (*new), KM_SLEEP);
+ int ret = 0;
+ void pm_dequeue_blocked(pm_rsvp_t *);
+ void pm_enqueue_blocked(pm_rsvp_t *);
+
+ ASSERT(!pm_processes_stopped);
+ ASSERT(PM_IAM_LOCKING_DIP(dip));
+ new->pr_dip = dip;
+ new->pr_comp = comp;
+ new->pr_newlevel = newpower;
+ new->pr_oldlevel = oldpower;
+ cv_init(&new->pr_cv, NULL, CV_DEFAULT, NULL);
+ mutex_enter(&pm_rsvp_lock);
+ pm_enqueue_blocked(new);
+ pm_enqueue_notify(PSC_PENDING_CHANGE, dip, comp, newpower, oldpower,
+ PM_CANBLOCK_BLOCK);
+ PM_UNLOCK_DIP(dip);
+ /*
+ * truss may make the cv_wait_sig return prematurely
+ */
+ while (ret == 0) {
+ /*
+ * Normally there will be no user context involved, but if
+ * there is (e.g. we are here via an ioctl call to a driver)
+ * then we should allow the process to abort the request,
+ * or we get an unkillable process if the same thread does
+ * PM_DIRECT_PM and pm_raise_power
+ */
+ if (cv_wait_sig(&new->pr_cv, &pm_rsvp_lock) == 0) {
+ ret = PMP_FAIL;
+ } else {
+ ret = new->pr_retval;
+ }
+ }
+ pm_dequeue_blocked(new);
+ mutex_exit(&pm_rsvp_lock);
+ cv_destroy(&new->pr_cv);
+ kmem_free(new, sizeof (*new));
+ return (ret);
+}
+
+/*
+ * Returns true if the process is interested in power level changes (has issued
+ * PM_GET_STATE_CHANGE ioctl).
+ */
+int
+pm_interest_registered(int clone)
+{
+ ASSERT(clone >= 0 && clone < PM_MAX_CLONE - 1);
+ return (pm_interest[clone]);
+}
+
+/*
+ * Process with clone has just done PM_DIRECT_PM on dip, or has asked to
+ * watch all state transitions (dip == NULL). Set up data
+ * structs to communicate with process about state changes.
+ */
+void
+pm_register_watcher(int clone, dev_info_t *dip)
+{
+ pscc_t *p;
+ psce_t *psce;
+ static void pm_enqueue_pscc(pscc_t *, pscc_t **);
+
+ /*
+ * We definitely need a control struct, then we have to search to see
+ * there is already an entries struct (in the dip != NULL case).
+ */
+ pscc_t *pscc = kmem_zalloc(sizeof (*pscc), KM_SLEEP);
+ pscc->pscc_clone = clone;
+ pscc->pscc_dip = dip;
+
+ if (dip) {
+ int found = 0;
+ rw_enter(&pm_pscc_direct_rwlock, RW_WRITER);
+ for (p = pm_pscc_direct; p; p = p->pscc_next) {
+ /*
+ * Already an entry for this clone, so just use it
+ * for the new one (for the case where a single
+ * process is watching multiple devices)
+ */
+ if (p->pscc_clone == clone) {
+ ASSERT(p->pscc_dip != dip);
+ pscc->pscc_entries = p->pscc_entries;
+ pscc->pscc_entries->psce_references++;
+ found++;
+ }
+ }
+ if (!found) { /* create a new one */
+ psce = kmem_zalloc(sizeof (psce_t), KM_SLEEP);
+ mutex_init(&psce->psce_lock, NULL, MUTEX_DEFAULT, NULL);
+ psce->psce_first =
+ kmem_zalloc(sizeof (pm_state_change_t) * PSCCOUNT,
+ KM_SLEEP);
+ psce->psce_in = psce->psce_out = psce->psce_first;
+ psce->psce_last = &psce->psce_first[PSCCOUNT - 1];
+ psce->psce_references = 1;
+ pscc->pscc_entries = psce;
+ }
+ pm_enqueue_pscc(pscc, &pm_pscc_direct);
+ rw_exit(&pm_pscc_direct_rwlock);
+ } else {
+ ASSERT(!pm_interest_registered(clone));
+ rw_enter(&pm_pscc_interest_rwlock, RW_WRITER);
+#ifdef DEBUG
+ for (p = pm_pscc_interest; p; p = p->pscc_next) {
+ /*
+ * Should not be an entry for this clone!
+ */
+ ASSERT(p->pscc_clone != clone);
+ }
+#endif
+ psce = kmem_zalloc(sizeof (psce_t), KM_SLEEP);
+ psce->psce_first = kmem_zalloc(sizeof (pm_state_change_t) *
+ PSCCOUNT, KM_SLEEP);
+ psce->psce_in = psce->psce_out = psce->psce_first;
+ psce->psce_last = &psce->psce_first[PSCCOUNT - 1];
+ psce->psce_references = 1;
+ pscc->pscc_entries = psce;
+ pm_enqueue_pscc(pscc, &pm_pscc_interest);
+ pm_interest[clone] = 1;
+ rw_exit(&pm_pscc_interest_rwlock);
+ }
+}
+
+/*
+ * Remove the given entry from the blocked list
+ */
+void
+pm_dequeue_blocked(pm_rsvp_t *p)
+{
+ ASSERT(MUTEX_HELD(&pm_rsvp_lock));
+ if (pm_blocked_list == p) {
+ ASSERT(p->pr_prev == NULL);
+ if (p->pr_next != NULL)
+ p->pr_next->pr_prev = NULL;
+ pm_blocked_list = p->pr_next;
+ } else {
+ ASSERT(p->pr_prev != NULL);
+ p->pr_prev->pr_next = p->pr_next;
+ if (p->pr_next != NULL)
+ p->pr_next->pr_prev = p->pr_prev;
+ }
+}
+
+/*
+ * Remove the given control struct from the given list
+ */
+static void
+pm_dequeue_pscc(pscc_t *p, pscc_t **list)
+{
+ if (*list == p) {
+ ASSERT(p->pscc_prev == NULL);
+ if (p->pscc_next != NULL)
+ p->pscc_next->pscc_prev = NULL;
+ *list = p->pscc_next;
+ } else {
+ ASSERT(p->pscc_prev != NULL);
+ p->pscc_prev->pscc_next = p->pscc_next;
+ if (p->pscc_next != NULL)
+ p->pscc_next->pscc_prev = p->pscc_prev;
+ }
+}
+
+/*
+ * Stick the control struct specified on the front of the list
+ */
+static void
+pm_enqueue_pscc(pscc_t *p, pscc_t **list)
+{
+ pscc_t *h; /* entry at head of list */
+ if ((h = *list) == NULL) {
+ *list = p;
+ ASSERT(p->pscc_next == NULL);
+ ASSERT(p->pscc_prev == NULL);
+ } else {
+ p->pscc_next = h;
+ ASSERT(h->pscc_prev == NULL);
+ h->pscc_prev = p;
+ ASSERT(p->pscc_prev == NULL);
+ *list = p;
+ }
+}
+
+/*
+ * If dip is NULL, process is closing "clone" clean up all its registrations.
+ * Otherwise only clean up those for dip because process is just giving up
+ * control of a direct device.
+ */
+void
+pm_deregister_watcher(int clone, dev_info_t *dip)
+{
+ pscc_t *p, *pn;
+ psce_t *psce;
+ int found = 0;
+
+ if (dip == NULL) {
+ rw_enter(&pm_pscc_interest_rwlock, RW_WRITER);
+ for (p = pm_pscc_interest; p; p = pn) {
+ pn = p->pscc_next;
+ if (p->pscc_clone == clone) {
+ pm_dequeue_pscc(p, &pm_pscc_interest);
+ psce = p->pscc_entries;
+ ASSERT(psce->psce_references == 1);
+ mutex_destroy(&psce->psce_lock);
+ kmem_free(psce->psce_first,
+ sizeof (pm_state_change_t) * PSCCOUNT);
+ kmem_free(psce, sizeof (*psce));
+ kmem_free(p, sizeof (*p));
+ }
+ }
+ pm_interest[clone] = 0;
+ rw_exit(&pm_pscc_interest_rwlock);
+ }
+ found = 0;
+ rw_enter(&pm_pscc_direct_rwlock, RW_WRITER);
+ for (p = pm_pscc_direct; p; p = pn) {
+ pn = p->pscc_next;
+ if ((dip && p->pscc_dip == dip) ||
+ (dip == NULL && clone == p->pscc_clone)) {
+ ASSERT(clone == p->pscc_clone);
+ found++;
+ /*
+ * Remove from control list
+ */
+ pm_dequeue_pscc(p, &pm_pscc_direct);
+ /*
+ * If we're the last reference, free the
+ * entries struct.
+ */
+ psce = p->pscc_entries;
+ ASSERT(psce);
+ if (psce->psce_references == 1) {
+ kmem_free(psce->psce_first,
+ PSCCOUNT * sizeof (pm_state_change_t));
+ kmem_free(psce, sizeof (*psce));
+ } else {
+ psce->psce_references--;
+ }
+ kmem_free(p, sizeof (*p));
+ }
+ }
+ ASSERT(dip == NULL || found);
+ rw_exit(&pm_pscc_direct_rwlock);
+}
+
+/*
+ * Search the indicated list for an entry that matches clone, and return a
+ * pointer to it. To be interesting, the entry must have something ready to
+ * be passed up to the controlling process.
+ * The returned entry will be locked upon return from this call.
+ */
+static psce_t *
+pm_psc_find_clone(int clone, pscc_t **list, krwlock_t *lock)
+{
+ pscc_t *p;
+ psce_t *psce;
+ rw_enter(lock, RW_READER);
+ for (p = *list; p; p = p->pscc_next) {
+ if (clone == p->pscc_clone) {
+ psce = p->pscc_entries;
+ mutex_enter(&psce->psce_lock);
+ if (psce->psce_out->size) {
+ rw_exit(lock);
+ return (psce);
+ } else {
+ mutex_exit(&psce->psce_lock);
+ }
+ }
+ }
+ rw_exit(lock);
+ return (NULL);
+}
+
+/*
+ * Find an entry for a particular clone in the direct list.
+ */
+psce_t *
+pm_psc_clone_to_direct(int clone)
+{
+ static psce_t *pm_psc_find_clone(int, pscc_t **, krwlock_t *);
+ return (pm_psc_find_clone(clone, &pm_pscc_direct,
+ &pm_pscc_direct_rwlock));
+}
+
+/*
+ * Find an entry for a particular clone in the interest list.
+ */
+psce_t *
+pm_psc_clone_to_interest(int clone)
+{
+ static psce_t *pm_psc_find_clone(int, pscc_t **, krwlock_t *);
+ return (pm_psc_find_clone(clone, &pm_pscc_interest,
+ &pm_pscc_interest_rwlock));
+}
+
+/*
+ * Put the given entry at the head of the blocked list
+ */
+void
+pm_enqueue_blocked(pm_rsvp_t *p)
+{
+ ASSERT(MUTEX_HELD(&pm_rsvp_lock));
+ ASSERT(p->pr_next == NULL);
+ ASSERT(p->pr_prev == NULL);
+ if (pm_blocked_list != NULL) {
+ p->pr_next = pm_blocked_list;
+ ASSERT(pm_blocked_list->pr_prev == NULL);
+ pm_blocked_list->pr_prev = p;
+ pm_blocked_list = p;
+ } else {
+ pm_blocked_list = p;
+ }
+}
+
+/*
+ * Sets every power managed device back to its default threshold
+ */
+void
+pm_all_to_default_thresholds(void)
+{
+ ddi_walk_devs(ddi_root_node(), pm_set_dev_thr_walk,
+ (void *) &pm_system_idle_threshold);
+}
+
+static int
+pm_set_dev_thr_walk(dev_info_t *dip, void *arg)
+{
+ int thr = (int)(*(int *)arg);
+
+ if (!PM_GET_PM_INFO(dip))
+ return (DDI_WALK_CONTINUE);
+ pm_set_device_threshold(dip, thr, PMC_DEF_THRESH);
+ return (DDI_WALK_CONTINUE);
+}
+
+/*
+ * Returns the current threshold value (in seconds) for the indicated component
+ */
+int
+pm_current_threshold(dev_info_t *dip, int comp, int *threshp)
+{
+ if (comp < 0 || comp >= PM_NUMCMPTS(dip)) {
+ return (DDI_FAILURE);
+ } else {
+ *threshp = cur_threshold(dip, comp);
+ return (DDI_SUCCESS);
+ }
+}
+
+/*
+ * To be called when changing the power level of a component of a device.
+ * On some platforms, changing power on one device may require that power
+ * be changed on other, related devices in the same transaction. Thus, we
+ * always pass this request to the platform power manager so that all the
+ * affected devices will be locked.
+ */
+void
+pm_lock_power(dev_info_t *dip, int *circp)
+{
+ power_req_t power_req;
+ int result;
+
+ power_req.request_type = PMR_PPM_LOCK_POWER;
+ power_req.req.ppm_lock_power_req.who = dip;
+ power_req.req.ppm_lock_power_req.circp = circp;
+ (void) pm_ctlops(PPM(dip), dip, DDI_CTLOPS_POWER, &power_req, &result);
+}
+
+/*
+ * Release the lock (or locks) acquired to change the power of a device.
+ * See comments for pm_lock_power.
+ */
+void
+pm_unlock_power(dev_info_t *dip, int circ)
+{
+ power_req_t power_req;
+ int result;
+
+ power_req.request_type = PMR_PPM_UNLOCK_POWER;
+ power_req.req.ppm_unlock_power_req.who = dip;
+ power_req.req.ppm_unlock_power_req.circ = circ;
+ (void) pm_ctlops(PPM(dip), dip, DDI_CTLOPS_POWER, &power_req, &result);
+}
+
+
+/*
+ * Attempt (without blocking) to acquire the lock(s) needed to change the
+ * power of a component of a device. See comments for pm_lock_power.
+ *
+ * Return: 1 if lock(s) acquired, 0 if not.
+ */
+int
+pm_try_locking_power(dev_info_t *dip, int *circp)
+{
+ power_req_t power_req;
+ int result;
+
+ power_req.request_type = PMR_PPM_TRY_LOCK_POWER;
+ power_req.req.ppm_lock_power_req.who = dip;
+ power_req.req.ppm_lock_power_req.circp = circp;
+ (void) pm_ctlops(PPM(dip), dip, DDI_CTLOPS_POWER, &power_req, &result);
+ return (result);
+}
+
+
+/*
+ * Lock power state of a device.
+ *
+ * The implementation handles a special case where another thread may have
+ * acquired the lock and created/launched this thread to do the work. If
+ * the lock cannot be acquired immediately, we check to see if this thread
+ * is registered as a borrower of the lock. If so, we may proceed without
+ * the lock. This assumes that the lending thread blocks on the completion
+ * of this thread.
+ *
+ * Note 1: for use by ppm only.
+ *
+ * Note 2: On failing to get the lock immediately, we search lock_loan list
+ * for curthread (as borrower of the lock). On a hit, we check that the
+ * lending thread already owns the lock we want. It is safe to compare
+ * devi_busy_thread and thread id of the lender because in the == case (the
+ * only one we care about) we know that the owner is blocked. Similarly,
+ * If we find that curthread isn't registered as a lock borrower, it is safe
+ * to use the blocking call (ndi_devi_enter) because we know that if we
+ * weren't already listed as a borrower (upstream on the call stack) we won't
+ * become one.
+ */
+void
+pm_lock_power_single(dev_info_t *dip, int *circp)
+{
+ lock_loan_t *cur;
+
+ /* if the lock is available, we are done. */
+ if (ndi_devi_tryenter(dip, circp))
+ return;
+
+ mutex_enter(&pm_loan_lock);
+ /* see if our thread is registered as a lock borrower. */
+ for (cur = lock_loan_head.pmlk_next; cur; cur = cur->pmlk_next)
+ if (cur->pmlk_borrower == curthread)
+ break;
+ mutex_exit(&pm_loan_lock);
+
+ /* if this thread not already registered, it is safe to block */
+ if (cur == NULL)
+ ndi_devi_enter(dip, circp);
+ else {
+ /* registered: does lender own the lock we want? */
+ if (cur->pmlk_lender == DEVI(dip)->devi_busy_thread) {
+ ASSERT(cur->pmlk_dip == NULL || cur->pmlk_dip == dip);
+ cur->pmlk_dip = dip;
+ } else /* no: just block for it */
+ ndi_devi_enter(dip, circp);
+
+ }
+}
+
+/*
+ * Drop the lock on the device's power state. See comment for
+ * pm_lock_power_single() for special implementation considerations.
+ *
+ * Note: for use by ppm only.
+ */
+void
+pm_unlock_power_single(dev_info_t *dip, int circ)
+{
+ lock_loan_t *cur;
+
+ /* optimization: mutex not needed to check empty list */
+ if (lock_loan_head.pmlk_next == NULL) {
+ ndi_devi_exit(dip, circ);
+ return;
+ }
+
+ mutex_enter(&pm_loan_lock);
+ /* see if our thread is registered as a lock borrower. */
+ for (cur = lock_loan_head.pmlk_next; cur; cur = cur->pmlk_next)
+ if (cur->pmlk_borrower == curthread)
+ break;
+ mutex_exit(&pm_loan_lock);
+
+ if (cur == NULL || cur->pmlk_dip != dip)
+ /* we acquired the lock directly, so return it */
+ ndi_devi_exit(dip, circ);
+}
+
+/*
+ * Try to take the lock for changing the power level of a component.
+ *
+ * Note: for use by ppm only.
+ */
+int
+pm_try_locking_power_single(dev_info_t *dip, int *circp)
+{
+ return (ndi_devi_tryenter(dip, circp));
+}
+
+#ifdef DEBUG
+/*
+ * The following are used only to print out data structures for debugging
+ */
+void
+prdeps(char *msg)
+{
+
+ pm_pdr_t *rp;
+ int i;
+
+ pm_log("pm_dep_head %s %p\n", msg, (void *)pm_dep_head);
+ for (rp = pm_dep_head; rp; rp = rp->pdr_next) {
+ pm_log("%p: %s keeper %s, kept %s, kept count %d, next %p\n",
+ (void *)rp, (rp->pdr_isprop ? "property" : "device"),
+ rp->pdr_keeper, rp->pdr_kept, rp->pdr_kept_count,
+ (void *)rp->pdr_next);
+ if (rp->pdr_kept_count != 0) {
+ pm_log("kept list = ");
+ i = 0;
+ while (i < rp->pdr_kept_count) {
+ pm_log("%s ", rp->pdr_kept_paths[i]);
+ i++;
+ }
+ pm_log("\n");
+ }
+ }
+}
+
+void
+pr_noinvol(char *hdr)
+{
+ pm_noinvol_t *ip;
+
+ pm_log("%s\n", hdr);
+ rw_enter(&pm_noinvol_rwlock, RW_READER);
+ for (ip = pm_noinvol_head; ip; ip = ip->ni_next)
+ pm_log("\tmaj %d, flags %x, noinvolpm %d %s\n",
+ ip->ni_major, ip->ni_flags, ip->ni_noinvolpm, ip->ni_path);
+ rw_exit(&pm_noinvol_rwlock);
+}
+#endif
+
+/*
+ * Attempt to apply the thresholds indicated by rp to the node specified by
+ * dip.
+ */
+void
+pm_apply_recorded_thresh(dev_info_t *dip, pm_thresh_rec_t *rp)
+{
+ PMD_FUNC(pmf, "apply_recorded_thresh")
+ int i, j;
+ int comps = PM_NUMCMPTS(dip);
+ struct pm_component *cp;
+ pm_pte_t *ep;
+ int pm_valid_thresh(dev_info_t *, pm_thresh_rec_t *);
+
+ PMD(PMD_THRESH, ("%s: part: %s@%s(%s#%d), rp %p, %s\n", pmf,
+ PM_DEVICE(dip), (void *)rp, rp->ptr_physpath))
+ PM_LOCK_DIP(dip);
+ if (!PM_GET_PM_INFO(dip) || PM_ISBC(dip) || !pm_valid_thresh(dip, rp)) {
+ PMD(PMD_FAIL, ("%s: part: %s@%s(%s#%d) PM_GET_PM_INFO %p\n",
+ pmf, PM_DEVICE(dip), (void*)PM_GET_PM_INFO(dip)))
+ PMD(PMD_FAIL, ("%s: part: %s@%s(%s#%d) PM_ISBC %d\n",
+ pmf, PM_DEVICE(dip), PM_ISBC(dip)))
+ PMD(PMD_FAIL, ("%s: part: %s@%s(%s#%d) pm_valid_thresh %d\n",
+ pmf, PM_DEVICE(dip), pm_valid_thresh(dip, rp)))
+ PM_UNLOCK_DIP(dip);
+ return;
+ }
+
+ ep = rp->ptr_entries;
+ /*
+ * Here we do the special case of a device threshold
+ */
+ if (rp->ptr_numcomps == 0) { /* PM_SET_DEVICE_THRESHOLD product */
+ ASSERT(ep && ep->pte_numthresh == 1);
+ PMD(PMD_THRESH, ("%s: set dev thr %s@%s(%s#%d) to 0x%x\n",
+ pmf, PM_DEVICE(dip), ep->pte_thresh[0]))
+ PM_UNLOCK_DIP(dip);
+ pm_set_device_threshold(dip, ep->pte_thresh[0], PMC_DEV_THRESH);
+ if (autopm_enabled)
+ pm_rescan(dip);
+ return;
+ }
+ for (i = 0; i < comps; i++) {
+ cp = PM_CP(dip, i);
+ for (j = 0; j < ep->pte_numthresh; j++) {
+ PMD(PMD_THRESH, ("%s: set thr %d for %s@%s(%s#%d)[%d] "
+ "to %x\n", pmf, j, PM_DEVICE(dip),
+ i, ep->pte_thresh[j]))
+ cp->pmc_comp.pmc_thresh[j + 1] = ep->pte_thresh[j];
+ }
+ ep++;
+ }
+ DEVI(dip)->devi_pm_flags &= PMC_THRESH_NONE;
+ DEVI(dip)->devi_pm_flags |= PMC_COMP_THRESH;
+ PM_UNLOCK_DIP(dip);
+
+ if (autopm_enabled)
+ pm_rescan(dip);
+}
+
+/*
+ * Returns true if the threshold specified by rp could be applied to dip
+ * (that is, the number of components and transitions are the same)
+ */
+int
+pm_valid_thresh(dev_info_t *dip, pm_thresh_rec_t *rp)
+{
+ PMD_FUNC(pmf, "valid_thresh")
+ int comps, i;
+ pm_component_t *cp;
+ pm_pte_t *ep;
+
+ if (!PM_GET_PM_INFO(dip) || PM_ISBC(dip)) {
+ PMD(PMD_ERROR, ("%s: %s: no pm_info or BC\n", pmf,
+ rp->ptr_physpath))
+ return (0);
+ }
+ /*
+ * Special case: we represent the PM_SET_DEVICE_THRESHOLD case by
+ * an entry with numcomps == 0, (since we don't know how many
+ * components there are in advance). This is always a valid
+ * spec.
+ */
+ if (rp->ptr_numcomps == 0) {
+ ASSERT(rp->ptr_entries && rp->ptr_entries->pte_numthresh == 1);
+ return (1);
+ }
+ if (rp->ptr_numcomps != (comps = PM_NUMCMPTS(dip))) {
+ PMD(PMD_ERROR, ("%s: comp # mm (dip %d cmd %d) for %s\n",
+ pmf, PM_NUMCMPTS(dip), rp->ptr_numcomps, rp->ptr_physpath))
+ return (0);
+ }
+ ep = rp->ptr_entries;
+ for (i = 0; i < comps; i++) {
+ cp = PM_CP(dip, i);
+ if ((ep + i)->pte_numthresh !=
+ cp->pmc_comp.pmc_numlevels - 1) {
+ PMD(PMD_ERROR, ("%s: %s[%d]: thresh=%d, record=%d\n",
+ pmf, rp->ptr_physpath, i,
+ cp->pmc_comp.pmc_numlevels - 1,
+ (ep + i)->pte_numthresh))
+ return (0);
+ }
+ }
+ return (1);
+}
+
+/*
+ * Remove any recorded threshold for device physpath
+ * We know there will be at most one.
+ */
+void
+pm_unrecord_threshold(char *physpath)
+{
+ pm_thresh_rec_t *pptr, *ptr;
+
+ rw_enter(&pm_thresh_rwlock, RW_WRITER);
+ for (pptr = NULL, ptr = pm_thresh_head; ptr; ptr = ptr->ptr_next) {
+ if (strcmp(physpath, ptr->ptr_physpath) == 0) {
+ if (pptr) {
+ pptr->ptr_next = ptr->ptr_next;
+ } else {
+ ASSERT(pm_thresh_head == ptr);
+ pm_thresh_head = ptr->ptr_next;
+ }
+ kmem_free(ptr, ptr->ptr_size);
+ break;
+ }
+ pptr = ptr;
+ }
+ rw_exit(&pm_thresh_rwlock);
+}
+
+/*
+ * Discard all recorded thresholds. We are returning to the default pm state.
+ */
+void
+pm_discard_thresholds(void)
+{
+ pm_thresh_rec_t *rp;
+ rw_enter(&pm_thresh_rwlock, RW_WRITER);
+ while (pm_thresh_head) {
+ rp = pm_thresh_head;
+ pm_thresh_head = rp->ptr_next;
+ kmem_free(rp, rp->ptr_size);
+ }
+ rw_exit(&pm_thresh_rwlock);
+}
+
+/*
+ * Discard all recorded dependencies. We are returning to the default pm state.
+ */
+void
+pm_discard_dependencies(void)
+{
+ pm_pdr_t *rp;
+ int i;
+ size_t length;
+
+#ifdef DEBUG
+ if (pm_debug & PMD_DEP)
+ prdeps("Before discard\n");
+#endif
+ ddi_walk_devs(ddi_root_node(), pm_discard_dep_walk, NULL);
+
+#ifdef DEBUG
+ if (pm_debug & PMD_DEP)
+ prdeps("After discard\n");
+#endif
+ while (pm_dep_head) {
+ rp = pm_dep_head;
+ if (!rp->pdr_isprop) {
+ ASSERT(rp->pdr_satisfied == 0);
+ ASSERT(pm_unresolved_deps);
+ pm_unresolved_deps--;
+ } else {
+ ASSERT(pm_prop_deps);
+ pm_prop_deps--;
+ }
+ pm_dep_head = rp->pdr_next;
+ if (rp->pdr_kept_count) {
+ for (i = 0; i < rp->pdr_kept_count; i++) {
+ length = strlen(rp->pdr_kept_paths[i]) + 1;
+ kmem_free(rp->pdr_kept_paths[i], length);
+ }
+ kmem_free(rp->pdr_kept_paths,
+ rp->pdr_kept_count * sizeof (char **));
+ }
+ kmem_free(rp, rp->pdr_size);
+ }
+}
+
+
+static int
+pm_discard_dep_walk(dev_info_t *dip, void *arg)
+{
+ _NOTE(ARGUNUSED(arg))
+ char *pathbuf;
+
+ if (PM_GET_PM_INFO(dip) == NULL)
+ return (DDI_WALK_CONTINUE);
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+ pm_free_keeper(pathbuf, 0);
+ kmem_free(pathbuf, MAXPATHLEN);
+ return (DDI_WALK_CONTINUE);
+}
+
+static int
+pm_kept_walk(dev_info_t *dip, void *arg)
+{
+ _NOTE(ARGUNUSED(arg))
+ char *pathbuf;
+
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+ (void) pm_kept(pathbuf);
+ kmem_free(pathbuf, MAXPATHLEN);
+
+ return (DDI_WALK_CONTINUE);
+}
+
+static int
+pm_keeper_walk(dev_info_t *dip, void *arg)
+{
+ _NOTE(ARGUNUSED(arg))
+ char *pathbuf;
+
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+ (void) pm_keeper(pathbuf);
+ kmem_free(pathbuf, MAXPATHLEN);
+
+ return (DDI_WALK_CONTINUE);
+}
+
+static char *
+pdw_type_decode(int type)
+{
+ switch (type) {
+ case PM_DEP_WK_POWER_ON:
+ return ("power on");
+ case PM_DEP_WK_POWER_OFF:
+ return ("power off");
+ case PM_DEP_WK_DETACH:
+ return ("detach");
+ case PM_DEP_WK_REMOVE_DEP:
+ return ("remove dep");
+ case PM_DEP_WK_BRINGUP_SELF:
+ return ("bringup self");
+ case PM_DEP_WK_RECORD_KEEPER:
+ return ("add dependent");
+ case PM_DEP_WK_RECORD_KEEPER_PROP:
+ return ("add dependent property");
+ case PM_DEP_WK_KEPT:
+ return ("kept");
+ case PM_DEP_WK_KEEPER:
+ return ("keeper");
+ case PM_DEP_WK_ATTACH:
+ return ("attach");
+ case PM_DEP_WK_CHECK_KEPT:
+ return ("check kept");
+ case PM_DEP_WK_CPR_SUSPEND:
+ return ("suspend");
+ case PM_DEP_WK_CPR_RESUME:
+ return ("resume");
+ default:
+ return ("unknown");
+ }
+
+}
+
+static void
+pm_rele_dep(char *keeper)
+{
+ PMD_FUNC(pmf, "rele_dep")
+ pm_pdr_t *dp;
+ char *kept_path = NULL;
+ dev_info_t *kept = NULL;
+ int count = 0;
+
+ for (dp = pm_dep_head; dp; dp = dp->pdr_next) {
+ if (strcmp(dp->pdr_keeper, keeper) != 0)
+ continue;
+ for (count = 0; count < dp->pdr_kept_count; count++) {
+ kept_path = dp->pdr_kept_paths[count];
+ if (kept_path == NULL)
+ continue;
+ kept = pm_name_to_dip(kept_path, 1);
+ if (kept) {
+ PMD(PMD_KEEPS, ("%s: release kept=%s@%s(%s#%d) "
+ "of keeper=%s\n", pmf, PM_DEVICE(kept),
+ keeper))
+ ASSERT(DEVI(kept)->devi_pm_kidsupcnt > 0);
+ pm_rele_power(kept);
+ ddi_release_devi(kept);
+ }
+ }
+ }
+}
+
+/*
+ * Called when we are just released from direct PM. Bring ourself up
+ * if our keeper is up since dependency is not honored while a kept
+ * device is under direct PM.
+ */
+static void
+pm_bring_self_up(char *keptpath)
+{
+ PMD_FUNC(pmf, "bring_self_up")
+ dev_info_t *kept;
+ dev_info_t *keeper;
+ pm_pdr_t *dp;
+ int i, j;
+ int up = 0, circ;
+
+ kept = pm_name_to_dip(keptpath, 1);
+ if (kept == NULL)
+ return;
+ PMD(PMD_KEEPS, ("%s: kept=%s@%s(%s#%d)\n", pmf, PM_DEVICE(kept)))
+ for (dp = pm_dep_head; dp; dp = dp->pdr_next) {
+ if (dp->pdr_kept_count == 0)
+ continue;
+ for (i = 0; i < dp->pdr_kept_count; i++) {
+ if (strcmp(dp->pdr_kept_paths[i], keptpath) != 0)
+ continue;
+ keeper = pm_name_to_dip(dp->pdr_keeper, 1);
+ if (keeper) {
+ PMD(PMD_KEEPS, ("%s: keeper=%s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(keeper)))
+ PM_LOCK_POWER(keeper, &circ);
+ for (j = 0; j < PM_NUMCMPTS(keeper);
+ j++) {
+ if (PM_CURPOWER(keeper, j)) {
+ PMD(PMD_KEEPS, ("%s: comp="
+ "%d is up\n", pmf, j))
+ up++;
+ }
+ }
+ if (up) {
+ if (PM_SKBU(kept))
+ DEVI(kept)->devi_pm_flags &=
+ ~PMC_SKIP_BRINGUP;
+ bring_pmdep_up(kept, 1);
+ }
+ PM_UNLOCK_POWER(keeper, circ);
+ ddi_release_devi(keeper);
+ }
+ }
+ }
+ ddi_release_devi(kept);
+}
+
+static void
+pm_process_dep_request(pm_dep_wk_t *work)
+{
+ PMD_FUNC(pmf, "dep_req")
+ int ret;
+
+ PMD(PMD_DEP, ("%s: work=%s\n", pmf,
+ pdw_type_decode(work->pdw_type)))
+ PMD(PMD_DEP, ("%s: keeper=%s, kept=%s\n", pmf,
+ (work->pdw_keeper ? work->pdw_keeper : "NULL"),
+ (work->pdw_kept ? work->pdw_kept : "NULL")))
+
+ switch (work->pdw_type) {
+ case PM_DEP_WK_POWER_ON:
+ /* Bring up the kept devices and put a hold on them */
+ bring_wekeeps_up(work->pdw_keeper);
+ break;
+ case PM_DEP_WK_POWER_OFF:
+ /* Release the kept devices */
+ pm_rele_dep(work->pdw_keeper);
+ break;
+ case PM_DEP_WK_DETACH:
+ pm_free_keeps(work->pdw_keeper, work->pdw_pwr);
+ break;
+ case PM_DEP_WK_REMOVE_DEP:
+ pm_discard_dependencies();
+ break;
+ case PM_DEP_WK_BRINGUP_SELF:
+ /*
+ * We deferred satisfying our dependency till now, so satisfy
+ * it again and bring ourselves up.
+ */
+ pm_bring_self_up(work->pdw_kept);
+ break;
+ case PM_DEP_WK_RECORD_KEEPER:
+ (void) pm_record_keeper(work->pdw_kept, work->pdw_keeper, 0);
+ ddi_walk_devs(ddi_root_node(), pm_kept_walk, NULL);
+ ddi_walk_devs(ddi_root_node(), pm_keeper_walk, NULL);
+ break;
+ case PM_DEP_WK_RECORD_KEEPER_PROP:
+ (void) pm_record_keeper(work->pdw_kept, work->pdw_keeper, 1);
+ ddi_walk_devs(ddi_root_node(), pm_keeper_walk, NULL);
+ ddi_walk_devs(ddi_root_node(), pm_kept_walk, NULL);
+ break;
+ case PM_DEP_WK_KEPT:
+ ret = pm_kept(work->pdw_kept);
+ PMD(PMD_DEP, ("%s: PM_DEP_WK_KEPT: pm_kept returns %d\n", pmf,
+ ret))
+ break;
+ case PM_DEP_WK_KEEPER:
+ ret = pm_keeper(work->pdw_keeper);
+ PMD(PMD_DEP, ("%s: PM_DEP_WK_KEEPER: pm_keeper returns %d\n",
+ pmf, ret))
+ break;
+ case PM_DEP_WK_ATTACH:
+ ret = pm_keeper(work->pdw_keeper);
+ PMD(PMD_DEP, ("%s: PM_DEP_WK_ATTACH: pm_keeper returns %d\n",
+ pmf, ret))
+ ret = pm_kept(work->pdw_kept);
+ PMD(PMD_DEP, ("%s: PM_DEP_WK_ATTACH: pm_kept returns %d\n",
+ pmf, ret))
+ break;
+ case PM_DEP_WK_CHECK_KEPT:
+ ret = pm_is_kept(work->pdw_kept);
+ PMD(PMD_DEP, ("%s: PM_DEP_WK_CHECK_KEPT: kept=%s, ret=%d\n",
+ pmf, work->pdw_kept, ret))
+ break;
+ case PM_DEP_WK_CPR_SUSPEND:
+ pm_discard_dependencies();
+ break;
+ case PM_DEP_WK_CPR_RESUME:
+ ddi_walk_devs(ddi_root_node(), pm_kept_walk, NULL);
+ ddi_walk_devs(ddi_root_node(), pm_keeper_walk, NULL);
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ /*
+ * Free the work structure if the requester is not waiting
+ * Otherwise it is the requester's responsiblity to free it.
+ */
+ if (!work->pdw_wait) {
+ if (work->pdw_keeper)
+ kmem_free(work->pdw_keeper,
+ strlen(work->pdw_keeper) + 1);
+ if (work->pdw_kept)
+ kmem_free(work->pdw_kept, strlen(work->pdw_kept) + 1);
+ kmem_free(work, sizeof (pm_dep_wk_t));
+ } else {
+ /*
+ * Notify requester if it is waiting for it.
+ */
+ work->pdw_ret = ret;
+ work->pdw_done = 1;
+ cv_signal(&work->pdw_cv);
+ }
+}
+
+/*
+ * Process PM dependency requests.
+ */
+static void
+pm_dep_thread(void)
+{
+ pm_dep_wk_t *work;
+ callb_cpr_t cprinfo;
+
+ CALLB_CPR_INIT(&cprinfo, &pm_dep_thread_lock, callb_generic_cpr,
+ "pm_dep_thread");
+ for (;;) {
+ mutex_enter(&pm_dep_thread_lock);
+ if (pm_dep_thread_workq == NULL) {
+ CALLB_CPR_SAFE_BEGIN(&cprinfo);
+ cv_wait(&pm_dep_thread_cv, &pm_dep_thread_lock);
+ CALLB_CPR_SAFE_END(&cprinfo, &pm_dep_thread_lock);
+ }
+ work = pm_dep_thread_workq;
+ pm_dep_thread_workq = work->pdw_next;
+ if (pm_dep_thread_tail == work)
+ pm_dep_thread_tail = work->pdw_next;
+ mutex_exit(&pm_dep_thread_lock);
+ pm_process_dep_request(work);
+
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * Set the power level of the indicated device to unknown (if it is not a
+ * backwards compatible device), as it has just been resumed, and it won't
+ * know if the power was removed or not. Adjust parent's kidsupcnt if necessary.
+ */
+void
+pm_forget_power_level(dev_info_t *dip)
+{
+ dev_info_t *pdip = ddi_get_parent(dip);
+ int i, count = 0;
+
+ if (!PM_ISBC(dip)) {
+ for (i = 0; i < PM_NUMCMPTS(dip); i++)
+ count += (PM_CURPOWER(dip, i) == 0);
+
+ if (count && pdip && !PM_WANTS_NOTIFICATION(pdip))
+ e_pm_hold_rele_power(pdip, count);
+
+ /*
+ * Count this as a power cycle if we care
+ */
+ if (DEVI(dip)->devi_pm_volpmd &&
+ PM_CP(dip, 0)->pmc_cur_pwr == 0)
+ DEVI(dip)->devi_pm_volpmd = 0;
+ for (i = 0; i < PM_NUMCMPTS(dip); i++)
+ e_pm_set_cur_pwr(dip, PM_CP(dip, i), PM_LEVEL_UNKNOWN);
+ }
+}
+
+/*
+ * This function advises the caller whether it should make a power-off
+ * transition at this time or not. If the transition is not advised
+ * at this time, the time that the next power-off transition can
+ * be made from now is returned through "intervalp" pointer.
+ * This function returns:
+ *
+ * 1 power-off advised
+ * 0 power-off not advised, intervalp will point to seconds from
+ * now that a power-off is advised. If it is passed the number
+ * of years that policy specifies the device should last,
+ * a large number is returned as the time interval.
+ * -1 error
+ */
+int
+pm_trans_check(struct pm_trans_data *datap, time_t *intervalp)
+{
+ PMD_FUNC(pmf, "pm_trans_check")
+ char dbuf[DC_SCSI_MFR_LEN];
+ struct pm_scsi_cycles *scp;
+ int service_years, service_weeks, full_years;
+ time_t now, service_seconds, tdiff;
+ time_t within_year, when_allowed;
+ char *ptr;
+ int lower_bound_cycles, upper_bound_cycles, cycles_allowed;
+ int cycles_diff, cycles_over;
+
+ if (datap == NULL) {
+ PMD(PMD_TCHECK, ("%s: NULL data pointer!\n", pmf))
+ return (-1);
+ }
+
+ if (datap->format == DC_SCSI_FORMAT) {
+ /*
+ * Power cycles of the scsi drives are distributed
+ * over 5 years with the following percentage ratio:
+ *
+ * 30%, 25%, 20%, 15%, and 10%
+ *
+ * The power cycle quota for each year is distributed
+ * linearly through out the year. The equation for
+ * determining the expected cycles is:
+ *
+ * e = a * (n / y)
+ *
+ * e = expected cycles
+ * a = allocated cycles for this year
+ * n = number of seconds since beginning of this year
+ * y = number of seconds in a year
+ *
+ * Note that beginning of the year starts the day that
+ * the drive has been put on service.
+ *
+ * If the drive has passed its expected cycles, we
+ * can determine when it can start to power cycle
+ * again to keep it on track to meet the 5-year
+ * life expectancy. The equation for determining
+ * when to power cycle is:
+ *
+ * w = y * (c / a)
+ *
+ * w = when it can power cycle again
+ * y = number of seconds in a year
+ * c = current number of cycles
+ * a = allocated cycles for the year
+ *
+ */
+ char pcnt[DC_SCSI_NPY] = { 30, 55, 75, 90, 100 };
+
+ scp = &datap->un.scsi_cycles;
+ PMD(PMD_TCHECK, ("%s: format=%d, lifemax=%d, ncycles=%d, "
+ "svc_date=%s, svc_flag=%d\n", pmf, datap->format,
+ scp->lifemax, scp->ncycles, scp->svc_date, scp->flag))
+ if (scp->ncycles < 0 || scp->flag != 0) {
+ PMD(PMD_TCHECK, ("%s: ncycles < 0 || flag != 0\n", pmf))
+ return (-1);
+ }
+
+ if (scp->ncycles > scp->lifemax) {
+ *intervalp = (LONG_MAX / hz);
+ return (0);
+ }
+
+ /*
+ * convert service date to time_t
+ */
+ bcopy(scp->svc_date, dbuf, DC_SCSI_YEAR_LEN);
+ dbuf[DC_SCSI_YEAR_LEN] = '\0';
+ ptr = dbuf;
+ service_years = stoi(&ptr) - EPOCH_YEAR;
+ bcopy(&scp->svc_date[DC_SCSI_YEAR_LEN], dbuf,
+ DC_SCSI_WEEK_LEN);
+ dbuf[DC_SCSI_WEEK_LEN] = '\0';
+
+ /*
+ * scsi standard does not specify WW data,
+ * could be (00-51) or (01-52)
+ */
+ ptr = dbuf;
+ service_weeks = stoi(&ptr);
+ if (service_years < 0 ||
+ service_weeks < 0 || service_weeks > 52) {
+ PMD(PMD_TCHECK, ("%s: service year %d and week %d\n",
+ pmf, service_years, service_weeks))
+ return (-1);
+ }
+
+ /*
+ * calculate service date in seconds-since-epoch,
+ * adding one day for each leap-year.
+ *
+ * (years-since-epoch + 2) fixes integer truncation,
+ * example: (8) leap-years during [1972, 2000]
+ * (2000 - 1970) = 30; and (30 + 2) / 4 = 8;
+ */
+ service_seconds = (service_years * DC_SPY) +
+ (service_weeks * DC_SPW) +
+ (((service_years + 2) / 4) * DC_SPD);
+
+ now = gethrestime_sec();
+ /*
+ * since the granularity of 'svc_date' is day not second,
+ * 'now' should be rounded up to full day.
+ */
+ now = ((now + DC_SPD -1) / DC_SPD) * DC_SPD;
+ if (service_seconds > now) {
+ PMD(PMD_TCHECK, ("%s: service date (%ld) later "
+ "than now (%ld)!\n", pmf, service_seconds, now))
+ return (-1);
+ }
+
+ tdiff = now - service_seconds;
+ PMD(PMD_TCHECK, ("%s: age is %ld sec\n", pmf, tdiff))
+
+ /*
+ * NOTE - Leap years are not considered in the calculations
+ * below.
+ */
+ full_years = (tdiff / DC_SPY);
+ if ((full_years >= DC_SCSI_NPY) &&
+ (scp->ncycles <= scp->lifemax))
+ return (1);
+
+ /*
+ * Determine what is the normal cycle usage for the
+ * device at the beginning and the end of this year.
+ */
+ lower_bound_cycles = (!full_years) ? 0 :
+ ((scp->lifemax * pcnt[full_years - 1]) / 100);
+ upper_bound_cycles = (scp->lifemax * pcnt[full_years]) / 100;
+
+ if (scp->ncycles <= lower_bound_cycles)
+ return (1);
+
+ /*
+ * The linear slope that determines how many cycles
+ * are allowed this year is number of seconds
+ * passed this year over total number of seconds in a year.
+ */
+ cycles_diff = (upper_bound_cycles - lower_bound_cycles);
+ within_year = (tdiff % DC_SPY);
+ cycles_allowed = lower_bound_cycles +
+ (((uint64_t)cycles_diff * (uint64_t)within_year) / DC_SPY);
+ PMD(PMD_TCHECK, ("%s: lived %d yrs and %ld secs\n", pmf,
+ full_years, within_year))
+ PMD(PMD_TCHECK, ("%s: # of cycles allowed %d\n", pmf,
+ cycles_allowed))
+
+ if (scp->ncycles <= cycles_allowed)
+ return (1);
+
+ /*
+ * The transition is not advised now but we can
+ * determine when the next transition can be made.
+ *
+ * Depending on how many cycles the device has been
+ * over-used, we may need to skip years with
+ * different percentage quota in order to determine
+ * when the next transition can be made.
+ */
+ cycles_over = (scp->ncycles - lower_bound_cycles);
+ while (cycles_over > cycles_diff) {
+ full_years++;
+ if (full_years >= DC_SCSI_NPY) {
+ *intervalp = (LONG_MAX / hz);
+ return (0);
+ }
+ cycles_over -= cycles_diff;
+ lower_bound_cycles = upper_bound_cycles;
+ upper_bound_cycles =
+ (scp->lifemax * pcnt[full_years]) / 100;
+ cycles_diff = (upper_bound_cycles - lower_bound_cycles);
+ }
+
+ /*
+ * The linear slope that determines when the next transition
+ * can be made is the relative position of used cycles within a
+ * year over total number of cycles within that year.
+ */
+ when_allowed = service_seconds + (full_years * DC_SPY) +
+ (((uint64_t)DC_SPY * (uint64_t)cycles_over) / cycles_diff);
+ *intervalp = (when_allowed - now);
+ if (*intervalp > (LONG_MAX / hz))
+ *intervalp = (LONG_MAX / hz);
+ PMD(PMD_TCHECK, ("%s: no cycle is allowed in %ld secs\n", pmf,
+ *intervalp))
+ return (0);
+ }
+
+ PMD(PMD_TCHECK, ("%s: unknown format!\n", pmf))
+ return (-1);
+}
+
+/*
+ * Nexus drivers call into pm framework to indicate which child driver is about
+ * to be installed. In some platforms, ppm may need to configure the hardware
+ * for successful installation of a driver.
+ */
+int
+pm_init_child(dev_info_t *dip)
+{
+ power_req_t power_req;
+
+ ASSERT(ddi_binding_name(dip));
+ ASSERT(ddi_get_name_addr(dip));
+ pm_ppm_claim(dip);
+ if (pm_ppm_claimed(dip)) { /* if ppm driver claims the node */
+ power_req.request_type = PMR_PPM_INIT_CHILD;
+ power_req.req.ppm_config_req.who = dip;
+ ASSERT(PPM(dip) != NULL);
+ return (pm_ctlops(PPM(dip), dip, DDI_CTLOPS_POWER, &power_req,
+ NULL));
+ } else {
+#ifdef DEBUG
+ /* pass it to the default handler so we can debug things */
+ power_req.request_type = PMR_PPM_INIT_CHILD;
+ power_req.req.ppm_config_req.who = dip;
+ (void) pm_ctlops(NULL, dip,
+ DDI_CTLOPS_POWER, &power_req, NULL);
+#endif
+ }
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Bring parent of a node that is about to be probed up to full power, and
+ * arrange for it to stay up until pm_post_probe() or pm_post_attach() decide
+ * it is time to let it go down again
+ */
+void
+pm_pre_probe(dev_info_t *dip, pm_ppm_cookie_t *cp)
+{
+ int result;
+ power_req_t power_req;
+
+ bzero(cp, sizeof (*cp));
+ cp->ppc_dip = dip;
+
+ pm_ppm_claim(dip);
+ if (pm_ppm_claimed(dip)) { /* if ppm driver claims the node */
+ power_req.request_type = PMR_PPM_PRE_PROBE;
+ power_req.req.ppm_config_req.who = dip;
+ ASSERT(PPM(dip) != NULL);
+ (void) pm_ctlops(PPM(dip), dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+ cp->ppc_ppm = PPM(dip);
+ } else {
+#ifdef DEBUG
+ /* pass it to the default handler so we can debug things */
+ power_req.request_type = PMR_PPM_PRE_PROBE;
+ power_req.req.ppm_config_req.who = dip;
+ (void) pm_ctlops(NULL, dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+#endif
+ cp->ppc_ppm = NULL;
+ }
+}
+
+int
+pm_pre_config(dev_info_t *dip, char *devnm)
+{
+ PMD_FUNC(pmf, "pre_config")
+ int ret;
+
+ if (MDI_VHCI(dip)) {
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ ret = mdi_power(dip, MDI_PM_PRE_CONFIG, NULL, devnm, 0);
+ return (ret == MDI_SUCCESS ? DDI_SUCCESS : DDI_FAILURE);
+ } else if (!PM_GET_PM_INFO(dip))
+ return (DDI_SUCCESS);
+
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ pm_hold_power(dip);
+ ret = pm_all_to_normal(dip, PM_CANBLOCK_BLOCK);
+ if (ret != DDI_SUCCESS)
+ pm_rele_power(dip);
+ return (ret);
+}
+
+/*
+ * This routine is called by devfs during its walk to unconfigue a node.
+ * If the call is due to auto mod_unloads and the dip is not at its
+ * full power, we return DDI_FAILURE to terminate the walk, otherwise
+ * return DDI_SUCCESS.
+ */
+int
+pm_pre_unconfig(dev_info_t *dip, int flags, int *held, char *devnm)
+{
+ PMD_FUNC(pmf, "pre_unconfig")
+ int ret;
+
+ if (MDI_VHCI(dip)) {
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d), flags=%x\n", pmf,
+ PM_DEVICE(dip), flags))
+ ret = mdi_power(dip, MDI_PM_PRE_UNCONFIG, held, devnm, flags);
+ return (ret == MDI_SUCCESS ? DDI_SUCCESS : DDI_FAILURE);
+ } else if (!PM_GET_PM_INFO(dip))
+ return (DDI_SUCCESS);
+
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d), flags=%x\n", pmf, PM_DEVICE(dip),
+ flags))
+ *held = 0;
+
+ /*
+ * If the dip is a leaf node, don't power it up.
+ */
+ if (!ddi_get_child(dip))
+ return (DDI_SUCCESS);
+
+ /*
+ * Do not power up the node if it is called due to auto-modunload.
+ */
+ if ((flags & NDI_AUTODETACH) && !pm_all_at_normal(dip))
+ return (DDI_FAILURE);
+
+ pm_hold_power(dip);
+ *held = 1;
+ ret = pm_all_to_normal(dip, PM_CANBLOCK_BLOCK);
+ if (ret != DDI_SUCCESS) {
+ pm_rele_power(dip);
+ *held = 0;
+ }
+ return (ret);
+}
+
+/*
+ * Notify ppm of attach action. Parent is already held at full power by
+ * probe action.
+ */
+void
+pm_pre_attach(dev_info_t *dip, pm_ppm_cookie_t *cp, ddi_attach_cmd_t cmd)
+{
+ static char *me = "pm_pre_attach";
+ power_req_t power_req;
+ int result;
+
+ /*
+ * Initialize and fill in the PPM cookie
+ */
+ bzero(cp, sizeof (*cp));
+ cp->ppc_cmd = (int)cmd;
+ cp->ppc_ppm = PPM(dip);
+ cp->ppc_dip = dip;
+
+ /*
+ * DDI_ATTACH and DDI_RESUME cmds need to call platform specific
+ * Power Management stuff. DDI_RESUME also has to purge it's
+ * powerlevel information.
+ */
+ switch (cmd) {
+ case DDI_ATTACH:
+ if (cp->ppc_ppm) { /* if ppm driver claims the node */
+ power_req.request_type = PMR_PPM_PRE_ATTACH;
+ power_req.req.ppm_config_req.who = dip;
+ ASSERT(PPM(dip));
+ (void) pm_ctlops(cp->ppc_ppm, dip, DDI_CTLOPS_POWER,
+ &power_req, &result);
+ }
+#ifdef DEBUG
+ else {
+ power_req.request_type = PMR_PPM_PRE_ATTACH;
+ power_req.req.ppm_config_req.who = dip;
+ (void) pm_ctlops(NULL, dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+ }
+#endif
+ break;
+ case DDI_RESUME:
+ pm_forget_power_level(dip);
+
+ if (cp->ppc_ppm) { /* if ppm driver claims the node */
+ power_req.request_type = PMR_PPM_PRE_RESUME;
+ power_req.req.resume_req.who = cp->ppc_dip;
+ power_req.req.resume_req.cmd =
+ (ddi_attach_cmd_t)cp->ppc_cmd;
+ ASSERT(PPM(cp->ppc_dip) == cp->ppc_ppm);
+ (void) pm_ctlops(cp->ppc_ppm, cp->ppc_dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+ }
+#ifdef DEBUG
+ else {
+ power_req.request_type = PMR_PPM_PRE_RESUME;
+ power_req.req.resume_req.who = cp->ppc_dip;
+ power_req.req.resume_req.cmd =
+ (ddi_attach_cmd_t)cp->ppc_cmd;
+ (void) pm_ctlops(NULL, cp->ppc_dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+ }
+#endif
+ break;
+
+ case DDI_PM_RESUME:
+ break;
+
+ default:
+ panic(me);
+ }
+}
+
+/*
+ * Nexus drivers call into pm framework to indicate which child driver is
+ * being uninstalled. In some platforms, ppm may need to reconfigure the
+ * hardware since the device driver is no longer installed.
+ */
+int
+pm_uninit_child(dev_info_t *dip)
+{
+ power_req_t power_req;
+
+ ASSERT(ddi_binding_name(dip));
+ ASSERT(ddi_get_name_addr(dip));
+ pm_ppm_claim(dip);
+ if (pm_ppm_claimed(dip)) { /* if ppm driver claims the node */
+ power_req.request_type = PMR_PPM_UNINIT_CHILD;
+ power_req.req.ppm_config_req.who = dip;
+ ASSERT(PPM(dip));
+ return (pm_ctlops(PPM(dip), dip, DDI_CTLOPS_POWER, &power_req,
+ NULL));
+ } else {
+#ifdef DEBUG
+ /* pass it to the default handler so we can debug things */
+ power_req.request_type = PMR_PPM_UNINIT_CHILD;
+ power_req.req.ppm_config_req.who = dip;
+ (void) pm_ctlops(NULL, dip, DDI_CTLOPS_POWER, &power_req, NULL);
+#endif
+ }
+ return (DDI_SUCCESS);
+}
+/*
+ * Decrement kidsupcnt so scan can turn the parent back off if it is idle
+ * Also notify ppm of result of probe if there is a ppm that cares
+ */
+void
+pm_post_probe(pm_ppm_cookie_t *cp, int ret, int probe_failed)
+{
+ _NOTE(ARGUNUSED(probe_failed))
+ int result;
+ power_req_t power_req;
+
+ if (cp->ppc_ppm) { /* if ppm driver claims the node */
+ power_req.request_type = PMR_PPM_POST_PROBE;
+ power_req.req.ppm_config_req.who = cp->ppc_dip;
+ power_req.req.ppm_config_req.result = ret;
+ ASSERT(PPM(cp->ppc_dip) == cp->ppc_ppm);
+ (void) pm_ctlops(cp->ppc_ppm, cp->ppc_dip, DDI_CTLOPS_POWER,
+ &power_req, &result);
+ }
+#ifdef DEBUG
+ else {
+ power_req.request_type = PMR_PPM_POST_PROBE;
+ power_req.req.ppm_config_req.who = cp->ppc_dip;
+ power_req.req.ppm_config_req.result = ret;
+ (void) pm_ctlops(NULL, cp->ppc_dip, DDI_CTLOPS_POWER,
+ &power_req, &result);
+ }
+#endif
+}
+
+void
+pm_post_config(dev_info_t *dip, char *devnm)
+{
+ PMD_FUNC(pmf, "post_config")
+
+ if (MDI_VHCI(dip)) {
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ (void) mdi_power(dip, MDI_PM_POST_CONFIG, NULL, devnm, 0);
+ return;
+ } else if (!PM_GET_PM_INFO(dip))
+ return;
+
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ pm_rele_power(dip);
+}
+
+void
+pm_post_unconfig(dev_info_t *dip, int held, char *devnm)
+{
+ PMD_FUNC(pmf, "post_unconfig")
+
+ if (MDI_VHCI(dip)) {
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d), held = %d\n", pmf,
+ PM_DEVICE(dip), held))
+ (void) mdi_power(dip, MDI_PM_POST_UNCONFIG, &held, devnm, 0);
+ return;
+ } else if (!PM_GET_PM_INFO(dip))
+ return;
+
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d), held = %d\n", pmf, PM_DEVICE(dip),
+ held))
+ if (!held)
+ return;
+ /*
+ * We have held power in pre_unconfig, release it here.
+ */
+ pm_rele_power(dip);
+}
+
+/*
+ * Notify ppm of result of attach if there is a ppm that cares
+ */
+void
+pm_post_attach(pm_ppm_cookie_t *cp, int ret)
+{
+ int result;
+ power_req_t power_req;
+ dev_info_t *dip;
+
+ if (cp->ppc_cmd != DDI_ATTACH)
+ return;
+
+ dip = cp->ppc_dip;
+
+ if (ret == DDI_SUCCESS) {
+ /*
+ * Attach succeeded, so proceed to doing post-attach pm tasks
+ */
+ if (PM_GET_PM_INFO(dip) == NULL)
+ (void) pm_start(dip);
+ } else {
+ /*
+ * Attach may have got pm started before failing
+ */
+ pm_stop(dip);
+ }
+
+ if (cp->ppc_ppm) { /* if ppm driver claims the node */
+ power_req.request_type = PMR_PPM_POST_ATTACH;
+ power_req.req.ppm_config_req.who = cp->ppc_dip;
+ power_req.req.ppm_config_req.result = ret;
+ ASSERT(PPM(cp->ppc_dip) == cp->ppc_ppm);
+ (void) pm_ctlops(cp->ppc_ppm, cp->ppc_dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+ }
+#ifdef DEBUG
+ else {
+ power_req.request_type = PMR_PPM_POST_ATTACH;
+ power_req.req.ppm_config_req.who = cp->ppc_dip;
+ power_req.req.ppm_config_req.result = ret;
+ (void) pm_ctlops(NULL, cp->ppc_dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+ }
+#endif
+}
+
+/*
+ * Notify ppm of attach action. Parent is already held at full power by
+ * probe action.
+ */
+void
+pm_pre_detach(dev_info_t *dip, ddi_detach_cmd_t cmd, pm_ppm_cookie_t *cp)
+{
+ int result;
+ power_req_t power_req;
+
+ bzero(cp, sizeof (*cp));
+ cp->ppc_dip = dip;
+ cp->ppc_cmd = (int)cmd;
+
+ switch (cmd) {
+ case DDI_DETACH:
+ pm_detaching(dip); /* suspend pm while detaching */
+ if (pm_ppm_claimed(dip)) { /* if ppm driver claims node */
+ power_req.request_type = PMR_PPM_PRE_DETACH;
+ power_req.req.ppm_config_req.who = dip;
+ ASSERT(PPM(dip));
+ (void) pm_ctlops(PPM(dip), dip, DDI_CTLOPS_POWER,
+ &power_req, &result);
+ cp->ppc_ppm = PPM(dip);
+ } else {
+#ifdef DEBUG
+ /* pass to the default handler so we can debug things */
+ power_req.request_type = PMR_PPM_PRE_DETACH;
+ power_req.req.ppm_config_req.who = dip;
+ (void) pm_ctlops(NULL, dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+#endif
+ cp->ppc_ppm = NULL;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/*
+ * Dip is either a leaf node that exported "no-involuntary-power-cycles" prop.,
+ * (if devi_pm_noinvol count is 0) or an ancestor of such a node. We need to
+ * make an entry to record the details, which includes certain flag settings.
+ */
+static void
+pm_record_invol_path(char *path, int flags, int noinvolpm, int volpmd,
+ int wasvolpmd, major_t major)
+{
+ PMD_FUNC(pmf, "record_invol_path")
+ major_t pm_path_to_major(char *);
+ size_t plen;
+ pm_noinvol_t *ip, *np, *pp;
+ pp = NULL;
+
+ plen = strlen(path) + 1;
+ np = kmem_zalloc(sizeof (*np), KM_SLEEP);
+ np->ni_size = plen;
+ np->ni_path = kmem_alloc(plen, KM_SLEEP);
+ np->ni_noinvolpm = noinvolpm;
+ np->ni_volpmd = volpmd;
+ np->ni_wasvolpmd = wasvolpmd;
+ np->ni_flags = flags;
+ (void) strcpy(np->ni_path, path);
+ /*
+ * If we haven't actually seen the node attached, it is hard to figure
+ * out its major. If we could hold the node by path, we would be much
+ * happier here.
+ */
+ if (major == (major_t)-1) {
+ np->ni_major = pm_path_to_major(path);
+ } else {
+ np->ni_major = major;
+ }
+ rw_enter(&pm_noinvol_rwlock, RW_WRITER);
+ for (ip = pm_noinvol_head; ip; pp = ip, ip = ip->ni_next) {
+ int comp = strcmp(path, ip->ni_path);
+ if (comp < 0) {
+ PMD(PMD_NOINVOL, ("%s: %s insert before %s\n",
+ pmf, path, ip->ni_path))
+ /* insert before current entry */
+ np->ni_next = ip;
+ if (pp) {
+ pp->ni_next = np;
+ } else {
+ pm_noinvol_head = np;
+ }
+ rw_exit(&pm_noinvol_rwlock);
+#ifdef DEBUG
+ if (pm_debug & PMD_NOINVOL)
+ pr_noinvol("record_invol_path exit0");
+#endif
+ return;
+ } else if (comp == 0) {
+ panic("%s already in pm_noinvol list", path);
+ }
+ }
+ /*
+ * If we did not find an entry in the list that this should go before,
+ * then it must go at the end
+ */
+ if (pp) {
+ PMD(PMD_NOINVOL, ("%s: %s append after %s\n", pmf, path,
+ pp->ni_path))
+ ASSERT(pp->ni_next == 0);
+ pp->ni_next = np;
+ } else {
+ PMD(PMD_NOINVOL, ("%s: %s added to end-of-list\n", pmf, path))
+ ASSERT(!pm_noinvol_head);
+ pm_noinvol_head = np;
+ }
+ rw_exit(&pm_noinvol_rwlock);
+#ifdef DEBUG
+ if (pm_debug & PMD_NOINVOL)
+ pr_noinvol("record_invol_path exit");
+#endif
+}
+
+void
+pm_record_invol(dev_info_t *dip)
+{
+ char *pathbuf;
+ int pm_all_components_off(dev_info_t *);
+ int volpmd = (PM_NUMCMPTS(dip) > 0) && pm_all_components_off(dip);
+
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+
+ pm_record_invol_path(pathbuf, (DEVI(dip)->devi_pm_flags &
+ (PMC_NO_INVOL | PMC_CONSOLE_FB)), DEVI(dip)->devi_pm_noinvolpm,
+ DEVI(dip)->devi_pm_volpmd, volpmd, PM_MAJOR(dip));
+
+ /*
+ * If this child's detach will be holding up its ancestors, then we
+ * allow for an exception to that if all children of this type have
+ * gone down voluntarily.
+ * Now walk down the tree incrementing devi_pm_noinvolpm
+ */
+ (void) pm_noinvol_update(PM_BP_NOINVOL_DETACH, 0, volpmd, pathbuf,
+ dip);
+ kmem_free(pathbuf, MAXPATHLEN);
+}
+
+void
+pm_post_detach(pm_ppm_cookie_t *cp, int ret)
+{
+ dev_info_t *dip = cp->ppc_dip;
+ int result;
+ power_req_t power_req;
+
+ switch (cp->ppc_cmd) {
+ case DDI_DETACH:
+ if (cp->ppc_ppm) { /* if ppm driver claims the node */
+ power_req.request_type = PMR_PPM_POST_DETACH;
+ power_req.req.ppm_config_req.who = cp->ppc_dip;
+ power_req.req.ppm_config_req.result = ret;
+ ASSERT(PPM(cp->ppc_dip) == cp->ppc_ppm);
+ (void) pm_ctlops(cp->ppc_ppm, cp->ppc_dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+ }
+#ifdef DEBUG
+ else {
+ power_req.request_type = PMR_PPM_POST_DETACH;
+ power_req.req.ppm_config_req.who = cp->ppc_dip;
+ power_req.req.ppm_config_req.result = ret;
+ (void) pm_ctlops(NULL, cp->ppc_dip,
+ DDI_CTLOPS_POWER, &power_req, &result);
+ }
+#endif
+ if (ret == DDI_SUCCESS) {
+ /*
+ * For hotplug detach we assume it is *really* gone
+ */
+ if (cp->ppc_cmd == DDI_DETACH &&
+ ((DEVI(dip)->devi_pm_flags &
+ (PMC_NO_INVOL | PMC_CONSOLE_FB)) ||
+ DEVI(dip)->devi_pm_noinvolpm))
+ pm_record_invol(dip);
+ DEVI(dip)->devi_pm_flags &=
+ ~(PMC_NO_INVOL | PMC_NOINVOL_DONE);
+
+ /*
+ * If console fb is detaching, then we don't need to
+ * worry any more about it going off (pm_detaching has
+ * brought up all components)
+ */
+ if (PM_IS_CFB(dip)) {
+ mutex_enter(&pm_cfb_lock);
+ ASSERT(cfb_dip_detaching);
+ ASSERT(cfb_dip == NULL);
+ ASSERT(pm_cfb_comps_off == 0);
+ cfb_dip_detaching = NULL;
+ mutex_exit(&pm_cfb_lock);
+ }
+ pm_stop(dip); /* make it permanent */
+ } else {
+ if (PM_IS_CFB(dip)) {
+ mutex_enter(&pm_cfb_lock);
+ ASSERT(cfb_dip_detaching);
+ ASSERT(cfb_dip == NULL);
+ ASSERT(pm_cfb_comps_off == 0);
+ cfb_dip = cfb_dip_detaching;
+ cfb_dip_detaching = NULL;
+ mutex_exit(&pm_cfb_lock);
+ }
+ pm_detach_failed(dip); /* resume power management */
+ }
+ break;
+ case DDI_PM_SUSPEND:
+ break;
+ case DDI_SUSPEND:
+ break; /* legal, but nothing to do */
+ default:
+#ifdef DEBUG
+ panic("pm_post_detach: unrecognized cmd %d for detach",
+ cp->ppc_cmd);
+ /*NOTREACHED*/
+#else
+ break;
+#endif
+ }
+}
+
+/*
+ * Called after vfs_mountroot has got the clock started to fix up timestamps
+ * that were set when root bush drivers attached. hresttime was 0 then, so the
+ * devices look busy but have a 0 busycnt
+ */
+int
+pm_adjust_timestamps(dev_info_t *dip, void *arg)
+{
+ _NOTE(ARGUNUSED(arg))
+
+ pm_info_t *info = PM_GET_PM_INFO(dip);
+ struct pm_component *cp;
+ int i;
+
+ if (!info)
+ return (DDI_WALK_CONTINUE);
+ PM_LOCK_BUSY(dip);
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ cp = PM_CP(dip, i);
+ if (cp->pmc_timestamp == 0 && cp->pmc_busycount == 0)
+ cp->pmc_timestamp = gethrestime_sec();
+ }
+ PM_UNLOCK_BUSY(dip);
+ return (DDI_WALK_CONTINUE);
+}
+
+/*
+ * Called at attach time to see if the device being attached has a record in
+ * the no involuntary power cycles list. If so, we do some bookkeeping on the
+ * parents and set a flag in the dip
+ */
+void
+pm_noinvol_specd(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "noinvol_specd")
+ char *pathbuf;
+ pm_noinvol_t *ip, *pp = NULL;
+ int wasvolpmd;
+ int found = 0;
+
+ if (DEVI(dip)->devi_pm_flags & PMC_NOINVOL_DONE)
+ return;
+ DEVI(dip)->devi_pm_flags |= PMC_NOINVOL_DONE;
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ (void) ddi_pathname(dip, pathbuf);
+
+ PM_LOCK_DIP(dip);
+ DEVI(dip)->devi_pm_volpmd = 0;
+ DEVI(dip)->devi_pm_noinvolpm = 0;
+ rw_enter(&pm_noinvol_rwlock, RW_READER);
+ for (ip = pm_noinvol_head; ip; pp = ip, ip = ip->ni_next) {
+ PMD(PMD_NOINVOL, ("%s: comparing '%s' to '%s'\n",
+ pmf, pathbuf, ip->ni_path))
+ if (strcmp(pathbuf, ip->ni_path) == 0) {
+ found++;
+ break;
+ }
+ }
+ rw_exit(&pm_noinvol_rwlock);
+ if (!found) {
+ PM_UNLOCK_DIP(dip);
+ kmem_free(pathbuf, MAXPATHLEN);
+ return;
+ }
+ rw_enter(&pm_noinvol_rwlock, RW_WRITER);
+ pp = NULL;
+ for (ip = pm_noinvol_head; ip; pp = ip, ip = ip->ni_next) {
+ PMD(PMD_NOINVOL, ("%s: comparing '%s' to '%s'\n",
+ pmf, pathbuf, ip->ni_path))
+ if (strcmp(pathbuf, ip->ni_path) == 0) {
+ ip->ni_flags &= ~PMC_DRIVER_REMOVED;
+ DEVI(dip)->devi_pm_flags |= ip->ni_flags;
+ /*
+ * Handle special case of console fb
+ */
+ if (PM_IS_CFB(dip)) {
+ mutex_enter(&pm_cfb_lock);
+ cfb_dip = dip;
+ PMD(PMD_CFB, ("%s: %s@%s(%s#%d) setting "
+ "cfb_dip\n", pmf, PM_DEVICE(dip)))
+ mutex_exit(&pm_cfb_lock);
+ }
+ DEVI(dip)->devi_pm_noinvolpm = ip->ni_noinvolpm;
+ ASSERT((DEVI(dip)->devi_pm_flags &
+ (PMC_NO_INVOL | PMC_CONSOLE_FB)) ||
+ DEVI(dip)->devi_pm_noinvolpm);
+ DEVI(dip)->devi_pm_volpmd = ip->ni_volpmd;
+ PMD(PMD_NOINVOL, ("%s: noinvol=%d, volpmd=%d, "
+ "wasvolpmd=%d, flags=%x, path=%s\n", pmf,
+ ip->ni_noinvolpm, ip->ni_volpmd,
+ ip->ni_wasvolpmd, ip->ni_flags, ip->ni_path))
+ /*
+ * free the entry in hopes the list will now be empty
+ * and we won't have to search it any more until the
+ * device detaches
+ */
+ if (pp) {
+ PMD(PMD_NOINVOL, ("%s: free %s, prev %s\n",
+ pmf, ip->ni_path, pp->ni_path))
+ pp->ni_next = ip->ni_next;
+ } else {
+ PMD(PMD_NOINVOL, ("%s: free %s head\n",
+ pmf, ip->ni_path))
+ ASSERT(pm_noinvol_head == ip);
+ pm_noinvol_head = ip->ni_next;
+ }
+ PM_UNLOCK_DIP(dip);
+ wasvolpmd = ip->ni_wasvolpmd;
+ rw_exit(&pm_noinvol_rwlock);
+ kmem_free(ip->ni_path, ip->ni_size);
+ kmem_free(ip, sizeof (*ip));
+ /*
+ * Now walk up the tree decrementing devi_pm_noinvolpm
+ * (and volpmd if appropriate)
+ */
+ (void) pm_noinvol_update(PM_BP_NOINVOL_ATTACH, 0,
+ wasvolpmd, pathbuf, dip);
+#ifdef DEBUG
+ if (pm_debug & PMD_NOINVOL)
+ pr_noinvol("noinvol_specd exit");
+#endif
+ kmem_free(pathbuf, MAXPATHLEN);
+ return;
+ }
+ }
+ kmem_free(pathbuf, MAXPATHLEN);
+ rw_exit(&pm_noinvol_rwlock);
+ PM_UNLOCK_DIP(dip);
+}
+
+int
+pm_all_components_off(dev_info_t *dip)
+{
+ int i;
+ pm_component_t *cp;
+
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ cp = PM_CP(dip, i);
+ if (cp->pmc_cur_pwr == PM_LEVEL_UNKNOWN ||
+ cp->pmc_comp.pmc_lvals[cp->pmc_cur_pwr])
+ return (0);
+ }
+ return (1); /* all off */
+}
+
+/*
+ * Make sure that all "no involuntary power cycles" devices are attached.
+ * Called before doing a cpr suspend to make sure the driver has a say about
+ * the power cycle
+ */
+int
+pm_reattach_noinvol(void)
+{
+ PMD_FUNC(pmf, "reattach_noinvol")
+ pm_noinvol_t *ip;
+ char *path;
+ dev_info_t *dip;
+
+ /*
+ * Prevent the modunload thread from unloading any modules until we
+ * have completely stopped all kernel threads.
+ */
+ modunload_disable();
+ for (ip = pm_noinvol_head; ip; ip = ip->ni_next) {
+ /*
+ * Forget we'v ever seen any entry
+ */
+ ip->ni_persistent = 0;
+ }
+restart:
+ rw_enter(&pm_noinvol_rwlock, RW_READER);
+ for (ip = pm_noinvol_head; ip; ip = ip->ni_next) {
+ major_t maj;
+ maj = ip->ni_major;
+ path = ip->ni_path;
+ if (path != NULL && !(ip->ni_flags & PMC_DRIVER_REMOVED)) {
+ if (ip->ni_persistent) {
+ /*
+ * If we weren't able to make this entry
+ * go away, then we give up, as
+ * holding/attaching the driver ought to have
+ * resulted in this entry being deleted
+ */
+ PMD(PMD_NOINVOL, ("%s: can't reattach %s "
+ "(%s|%d)\n", pmf, ip->ni_path,
+ ddi_major_to_name(maj), (int)maj))
+ cmn_err(CE_WARN, "cpr: unable to reattach %s ",
+ ip->ni_path);
+ modunload_enable();
+ rw_exit(&pm_noinvol_rwlock);
+ return (0);
+ }
+ ip->ni_persistent++;
+ rw_exit(&pm_noinvol_rwlock);
+ PMD(PMD_NOINVOL, ("%s: holding %s\n", pmf, path))
+ dip = e_ddi_hold_devi_by_path(path, 0);
+ if (dip == NULL) {
+ PMD(PMD_NOINVOL, ("%s: can't hold (%s|%d)\n",
+ pmf, path, (int)maj))
+ cmn_err(CE_WARN, "cpr: unable to hold %s "
+ "driver", path);
+ modunload_enable();
+ return (0);
+ } else {
+ PMD(PMD_DHR, ("%s: release %s\n", pmf, path))
+ /*
+ * Since the modunload thread is stopped, we
+ * don't have to keep the driver held, which
+ * saves a ton of bookkeeping
+ */
+ ddi_release_devi(dip);
+ goto restart;
+ }
+ } else {
+ PMD(PMD_NOINVOL, ("%s: skip %s; unknown major\n",
+ pmf, ip->ni_path))
+ continue;
+ }
+ }
+ rw_exit(&pm_noinvol_rwlock);
+ return (1);
+}
+
+void
+pm_reattach_noinvol_fini(void)
+{
+ modunload_enable();
+}
+
+/*
+ * Display pm support code
+ */
+
+
+/*
+ * console frame-buffer power-mgmt gets enabled when debugging
+ * services are not present or console fbpm override is set
+ */
+void
+pm_cfb_setup(const char *stdout_path)
+{
+ PMD_FUNC(pmf, "cfb_setup")
+ extern int obpdebug;
+ char *devname;
+ dev_info_t *dip;
+ int devname_len;
+ extern dev_info_t *fbdip;
+
+ /*
+ * By virtue of this function being called (from consconfig),
+ * we know stdout is a framebuffer.
+ */
+ stdout_is_framebuffer = 1;
+
+ if (obpdebug || (boothowto & RB_DEBUG)) {
+ if (pm_cfb_override == 0) {
+ /*
+ * Console is frame buffer, but we want to suppress
+ * pm on it because of debugging setup
+ */
+ pm_cfb_enabled = 0;
+ cmn_err(CE_NOTE, "Kernel debugger present: disabling "
+ "console power management.");
+ /*
+ * however, we still need to know which is the console
+ * fb in order to suppress pm on it
+ */
+ } else {
+ cmn_err(CE_WARN, "Kernel debugger present: see "
+ "kmdb(1M) for interaction with power management.");
+ }
+ }
+#ifdef DEBUG
+ /*
+ * IF console is fb and is power managed, don't do prom_printfs from
+ * pm debug macro
+ */
+ if (pm_cfb_enabled) {
+ if (pm_debug)
+ prom_printf("pm debug output will be to log only\n");
+ pm_divertdebug++;
+ }
+#endif
+ devname = i_ddi_strdup((char *)stdout_path, KM_SLEEP);
+ devname_len = strlen(devname) + 1;
+ PMD(PMD_CFB, ("%s: stripped %s\n", pmf, devname))
+ /* if the driver is attached */
+ if ((dip = fbdip) != NULL) {
+ PMD(PMD_CFB, ("%s: attached: %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+ /*
+ * We set up here as if the driver were power manageable in case
+ * we get a later attach of a pm'able driver (which would result
+ * in a panic later)
+ */
+ cfb_dip = dip;
+ DEVI(dip)->devi_pm_flags |= (PMC_CONSOLE_FB | PMC_NO_INVOL);
+ PMD(PMD_CFB, ("%s: cfb_dip -> %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+#ifdef DEBUG
+ if (!(PM_GET_PM_INFO(dip) != NULL && PM_NUMCMPTS(dip))) {
+ PMD(PMD_CFB, ("%s: %s@%s(%s#%d) not power-managed\n",
+ pmf, PM_DEVICE(dip)))
+ }
+#endif
+ } else {
+ char *ep;
+ PMD(PMD_CFB, ("%s: pntd %s failed\n", pmf, devname))
+ pm_record_invol_path(devname,
+ (PMC_CONSOLE_FB | PMC_NO_INVOL), 1, 0, 0,
+ (major_t)-1);
+ for (ep = strrchr(devname, '/'); ep != devname;
+ ep = strrchr(devname, '/')) {
+ PMD(PMD_CFB, ("%s: devname %s\n", pmf, devname))
+ *ep = '\0';
+ dip = pm_name_to_dip(devname, 0);
+ if (dip != NULL) {
+ /*
+ * Walk up the tree incrementing
+ * devi_pm_noinvolpm
+ */
+ (void) pm_noinvol_update(PM_BP_NOINVOL_CFB,
+ 0, 0, devname, dip);
+ break;
+ } else {
+ pm_record_invol_path(devname,
+ PMC_NO_INVOL, 1, 0, 0, (major_t)-1);
+ }
+ }
+ }
+ kmem_free(devname, devname_len);
+}
+
+void
+pm_cfb_rele(void)
+{
+ mutex_enter(&pm_cfb_lock);
+ /*
+ * this call isn't using the console any more, it is ok to take it
+ * down if the count goes to 0
+ */
+ cfb_inuse--;
+ mutex_exit(&pm_cfb_lock);
+}
+
+/*
+ * software interrupt handler for fbpm; this function exists because we can't
+ * bring up the frame buffer power from above lock level. So if we need to,
+ * we instead schedule a softint that runs this routine and takes us into
+ * debug_enter (a bit delayed from the original request, but avoiding a panic).
+ */
+static uint_t
+pm_cfb_softint(caddr_t int_handler_arg)
+{
+ _NOTE(ARGUNUSED(int_handler_arg))
+ int rval = DDI_INTR_UNCLAIMED;
+
+ mutex_enter(&pm_cfb_lock);
+ if (pm_soft_pending) {
+ mutex_exit(&pm_cfb_lock);
+ debug_enter((char *)NULL);
+ /* acquired in debug_enter before calling pm_cfb_trigger */
+ pm_cfb_rele();
+ mutex_enter(&pm_cfb_lock);
+ pm_soft_pending = 0;
+ mutex_exit(&pm_cfb_lock);
+ rval = DDI_INTR_CLAIMED;
+ } else
+ mutex_exit(&pm_cfb_lock);
+
+ return (rval);
+}
+
+void
+pm_cfb_setup_intr(void)
+{
+ PMD_FUNC(pmf, "cfb_setup_intr")
+ extern void prom_set_outfuncs(void (*)(void), void (*)(void));
+ void pm_cfb_check_and_powerup(void);
+
+ if (!stdout_is_framebuffer) {
+ PMD(PMD_CFB, ("%s: console not fb\n", pmf))
+ return;
+ }
+ mutex_init(&pm_cfb_lock, NULL, MUTEX_SPIN, (void *)ipltospl(SPL8));
+#ifdef DEBUG
+ mutex_init(&pm_debug_lock, NULL, MUTEX_SPIN, (void *)ipltospl(SPL8));
+#endif
+ /*
+ * setup software interrupt handler
+ */
+ if (ddi_add_softintr(ddi_root_node(), DDI_SOFTINT_HIGH, &pm_soft_id,
+ NULL, NULL, pm_cfb_softint, NULL) != DDI_SUCCESS)
+ panic("pm: unable to register soft intr.");
+
+ prom_set_outfuncs(pm_cfb_check_and_powerup, pm_cfb_rele);
+}
+
+/*
+ * Checks to see if it is safe to write to the console wrt power management
+ * (i.e. if the console is a framebuffer, then it must be at full power)
+ * returns 1 when power is off (power-up is needed)
+ * returns 0 when power is on (power-up not needed)
+ */
+int
+pm_cfb_check_and_hold(void)
+{
+ /*
+ * cfb_dip is set iff console is a power manageable frame buffer
+ * device
+ */
+ extern int modrootloaded;
+
+ mutex_enter(&pm_cfb_lock);
+ cfb_inuse++;
+ ASSERT(cfb_inuse); /* wrap? */
+ if (modrootloaded && cfb_dip) {
+ /*
+ * don't power down the frame buffer, the prom is using it
+ */
+ if (pm_cfb_comps_off) {
+ mutex_exit(&pm_cfb_lock);
+ return (1);
+ }
+ }
+ mutex_exit(&pm_cfb_lock);
+ return (0);
+}
+
+/*
+ * turn on cfb power (which is known to be off).
+ * Must be called below lock level!
+ */
+void
+pm_cfb_powerup(void)
+{
+ pm_info_t *info;
+ int norm;
+ int ccount, ci;
+ int unused;
+#ifdef DEBUG
+ /*
+ * Can't reenter prom_prekern, so suppress pm debug messages
+ * (still go to circular buffer).
+ */
+ mutex_enter(&pm_debug_lock);
+ pm_divertdebug++;
+ mutex_exit(&pm_debug_lock);
+#endif
+ info = PM_GET_PM_INFO(cfb_dip);
+ ASSERT(info);
+
+ ccount = PM_NUMCMPTS(cfb_dip);
+ for (ci = 0; ci < ccount; ci++) {
+ norm = pm_get_normal_power(cfb_dip, ci);
+ (void) pm_set_power(cfb_dip, ci, norm, PM_LEVEL_UPONLY,
+ PM_CANBLOCK_BYPASS, 0, &unused);
+ }
+#ifdef DEBUG
+ mutex_enter(&pm_debug_lock);
+ pm_divertdebug--;
+ mutex_exit(&pm_debug_lock);
+#endif
+}
+
+/*
+ * Check if the console framebuffer is powered up. If not power it up.
+ * Note: Calling pm_cfb_check_and_hold has put a hold on the power state which
+ * must be released by calling pm_cfb_rele when the console fb operation
+ * is completed.
+ */
+void
+pm_cfb_check_and_powerup(void)
+{
+ if (pm_cfb_check_and_hold())
+ pm_cfb_powerup();
+}
+
+/*
+ * Trigger a low level interrupt to power up console frame buffer.
+ */
+void
+pm_cfb_trigger(void)
+{
+ if (cfb_dip == NULL)
+ return;
+
+ mutex_enter(&pm_cfb_lock);
+ /*
+ * If machine appears to be hung, pulling the keyboard connector of
+ * the console will cause a high level interrupt and go to debug_enter.
+ * But, if the fb is powered down, this routine will be called to bring
+ * it up (by generating a softint to do the work). If soft interrupts
+ * are not running, and the keyboard connector is pulled again, the
+ * following code detects this condition and calls panic which allows
+ * the fb to be brought up from high level.
+ *
+ * If two nearly simultaneous calls to debug_enter occur (both from
+ * high level) the code described above will cause a panic.
+ */
+ if (lbolt <= pm_soft_pending) {
+ panicstr = "pm_cfb_trigger: lbolt not advancing";
+ panic(panicstr); /* does a power up at any intr level */
+ /* NOTREACHED */
+ }
+ pm_soft_pending = lbolt;
+ mutex_exit(&pm_cfb_lock);
+ ddi_trigger_softintr(pm_soft_id);
+}
+
+major_t
+pm_path_to_major(char *path)
+{
+ PMD_FUNC(pmf, "path_to_major")
+ char *np, *ap, *bp;
+ major_t ret;
+ size_t len;
+ static major_t i_path_to_major(char *, char *);
+
+ PMD(PMD_NOINVOL, ("%s: %s\n", pmf, path))
+
+ np = strrchr(path, '/');
+ if (np != NULL)
+ np++;
+ else
+ np = path;
+ len = strlen(np) + 1;
+ bp = kmem_alloc(len, KM_SLEEP);
+ (void) strcpy(bp, np);
+ if ((ap = strchr(bp, '@')) != NULL) {
+ *ap = '\0';
+ }
+ PMD(PMD_NOINVOL, ("%s: %d\n", pmf, ddi_name_to_major(np)))
+ ret = i_path_to_major(path, np);
+ kmem_free(bp, len);
+ return (ret);
+}
+
+#ifdef DEBUG
+
+char *pm_msgp;
+char *pm_bufend;
+char *pm_msgbuf = NULL;
+int pm_logpages = 2;
+
+#define PMLOGPGS pm_logpages
+
+/*PRINTFLIKE1*/
+void
+pm_log(const char *fmt, ...)
+{
+ va_list adx;
+ size_t size;
+
+ mutex_enter(&pm_debug_lock);
+ if (pm_msgbuf == NULL) {
+ pm_msgbuf = kmem_zalloc(mmu_ptob(PMLOGPGS), KM_SLEEP);
+ pm_bufend = pm_msgbuf + mmu_ptob(PMLOGPGS) - 1;
+ pm_msgp = pm_msgbuf;
+ }
+ va_start(adx, fmt);
+ size = vsnprintf(NULL, 0, fmt, adx) + 1;
+ va_end(adx);
+ va_start(adx, fmt);
+ if (size > (pm_bufend - pm_msgp)) { /* wraps */
+ bzero(pm_msgp, pm_bufend - pm_msgp);
+ (void) vsnprintf(pm_msgbuf, size, fmt, adx);
+ if (!pm_divertdebug)
+ prom_printf("%s", pm_msgp);
+ pm_msgp = pm_msgbuf + size;
+ } else {
+ (void) vsnprintf(pm_msgp, size, fmt, adx);
+ if (!pm_divertdebug)
+ prom_printf("%s", pm_msgp);
+ pm_msgp += size;
+ }
+ va_end(adx);
+ mutex_exit(&pm_debug_lock);
+}
+#endif /* DEBUG */
+
+/*
+ * We want to save the state of any directly pm'd devices over the suspend/
+ * resume process so that we can put them back the way the controlling
+ * process left them.
+ */
+void
+pm_save_direct_levels(void)
+{
+ pm_processes_stopped = 1;
+ ddi_walk_devs(ddi_root_node(), pm_save_direct_lvl_walk, 0);
+}
+
+static int
+pm_save_direct_lvl_walk(dev_info_t *dip, void *arg)
+{
+ _NOTE(ARGUNUSED(arg))
+ int i;
+ int *ip;
+ pm_info_t *info = PM_GET_PM_INFO(dip);
+
+ if (!info)
+ return (DDI_WALK_CONTINUE);
+
+ if (PM_ISDIRECT(dip) && !PM_ISBC(dip)) {
+ if (PM_NUMCMPTS(dip) > 2) {
+ info->pmi_lp = kmem_alloc(PM_NUMCMPTS(dip) *
+ sizeof (int), KM_SLEEP);
+ ip = info->pmi_lp;
+ } else {
+ ip = info->pmi_levels;
+ }
+ /* autopm and processes are stopped, ok not to lock power */
+ for (i = 0; i < PM_NUMCMPTS(dip); i++)
+ *ip++ = PM_CURPOWER(dip, i);
+ /*
+ * There is a small window between stopping the
+ * processes and setting pm_processes_stopped where
+ * a driver could get hung up in a pm_raise_power()
+ * call. Free any such driver now.
+ */
+ pm_proceed(dip, PMP_RELEASE, -1, -1);
+ }
+
+ return (DDI_WALK_CONTINUE);
+}
+
+void
+pm_restore_direct_levels(void)
+{
+ /*
+ * If cpr didn't call pm_save_direct_levels, (because stopping user
+ * threads failed) then we don't want to try to restore them
+ */
+ if (!pm_processes_stopped)
+ return;
+
+ ddi_walk_devs(ddi_root_node(), pm_restore_direct_lvl_walk, 0);
+ pm_processes_stopped = 0;
+}
+
+static int
+pm_restore_direct_lvl_walk(dev_info_t *dip, void *arg)
+{
+ _NOTE(ARGUNUSED(arg))
+ PMD_FUNC(pmf, "restore_direct_lvl_walk")
+ int i, nc, result;
+ int *ip;
+
+ pm_info_t *info = PM_GET_PM_INFO(dip);
+ if (!info)
+ return (DDI_WALK_CONTINUE);
+
+ if (PM_ISDIRECT(dip) && !PM_ISBC(dip)) {
+ if ((nc = PM_NUMCMPTS(dip)) > 2) {
+ ip = &info->pmi_lp[nc - 1];
+ } else {
+ ip = &info->pmi_levels[nc - 1];
+ }
+ /*
+ * Because fb drivers fail attempts to turn off the
+ * fb when the monitor is on, but treat a request to
+ * turn on the monitor as a request to turn on the
+ * fb too, we process components in descending order
+ * Because autopm is disabled and processes aren't
+ * running, it is ok to examine current power outside
+ * of the power lock
+ */
+ for (i = nc - 1; i >= 0; i--, ip--) {
+ if (PM_CURPOWER(dip, i) == *ip)
+ continue;
+ if (pm_set_power(dip, i, *ip, PM_LEVEL_EXACT,
+ PM_CANBLOCK_BYPASS, 0, &result) !=
+ DDI_SUCCESS) {
+ cmn_err(CE_WARN, "cpr: unable "
+ "to restore power level of "
+ "component %d of directly "
+ "power manged device %s@%s"
+ " to %d",
+ i, PM_NAME(dip),
+ PM_ADDR(dip), *ip);
+ PMD(PMD_FAIL, ("%s: failed to restore "
+ "%s@%s(%s#%d)[%d] exact(%d)->%d, "
+ "errno %d\n", pmf, PM_DEVICE(dip), i,
+ PM_CURPOWER(dip, i), *ip, result))
+ }
+ }
+ if (nc > 2) {
+ kmem_free(info->pmi_lp, nc * sizeof (int));
+ info->pmi_lp = NULL;
+ }
+ }
+ return (DDI_WALK_CONTINUE);
+}
+
+/*
+ * Stolen from the bootdev module
+ * attempt to convert a path to a major number
+ */
+static major_t
+i_path_to_major(char *path, char *leaf_name)
+{
+ extern major_t path_to_major(char *pathname);
+ major_t maj;
+
+ if ((maj = path_to_major(path)) == (major_t)-1) {
+ maj = ddi_name_to_major(leaf_name);
+ }
+
+ return (maj);
+}
+
+/*
+ * When user calls rem_drv, we need to forget no-involuntary-power-cycles state
+ * An entry in the list means that the device is detached, so we need to
+ * adjust its ancestors as if they had just seen this attach, and any detached
+ * ancestors need to have their list entries adjusted.
+ */
+void
+pm_driver_removed(major_t major)
+{
+ static void i_pm_driver_removed(major_t major);
+
+ /*
+ * Serialize removal of drivers. This is to keep ancestors of
+ * a node that is being deleted from getting deleted and added back
+ * with different counters.
+ */
+ mutex_enter(&pm_remdrv_lock);
+ i_pm_driver_removed(major);
+ mutex_exit(&pm_remdrv_lock);
+}
+
+/*
+ * This routine is called recursively by pm_noinvol_process_ancestors()
+ */
+static void
+i_pm_driver_removed(major_t major)
+{
+ PMD_FUNC(pmf, "driver_removed")
+ static void adjust_ancestors(char *, int);
+ static int pm_is_noinvol_ancestor(pm_noinvol_t *);
+ static void pm_noinvol_process_ancestors(char *);
+ pm_noinvol_t *ip, *pp = NULL;
+ int wasvolpmd;
+ ASSERT(major != (major_t)-1);
+ PMD(PMD_NOINVOL, ("%s: %s\n", pmf, ddi_major_to_name(major)))
+again:
+ rw_enter(&pm_noinvol_rwlock, RW_WRITER);
+ for (ip = pm_noinvol_head; ip; pp = ip, ip = ip->ni_next) {
+ if (major != ip->ni_major)
+ continue;
+ /*
+ * If it is an ancestor of no-invol node, which is
+ * not removed, skip it. This is to cover the case of
+ * ancestor removed without removing its descendants.
+ */
+ if (pm_is_noinvol_ancestor(ip)) {
+ ip->ni_flags |= PMC_DRIVER_REMOVED;
+ continue;
+ }
+ wasvolpmd = ip->ni_wasvolpmd;
+ /*
+ * remove the entry from the list
+ */
+ if (pp) {
+ PMD(PMD_NOINVOL, ("%s: freeing %s, prev is %s\n",
+ pmf, ip->ni_path, pp->ni_path))
+ pp->ni_next = ip->ni_next;
+ } else {
+ PMD(PMD_NOINVOL, ("%s: free %s head\n", pmf,
+ ip->ni_path))
+ ASSERT(pm_noinvol_head == ip);
+ pm_noinvol_head = ip->ni_next;
+ }
+ rw_exit(&pm_noinvol_rwlock);
+ adjust_ancestors(ip->ni_path, wasvolpmd);
+ /*
+ * Had an ancestor been removed before this node, it would have
+ * been skipped. Adjust the no-invol counters for such skipped
+ * ancestors.
+ */
+ pm_noinvol_process_ancestors(ip->ni_path);
+ kmem_free(ip->ni_path, ip->ni_size);
+ kmem_free(ip, sizeof (*ip));
+ goto again;
+ }
+ rw_exit(&pm_noinvol_rwlock);
+}
+
+/*
+ * returns 1, if *aip is a ancestor of a no-invol node
+ * 0, otherwise
+ */
+static int
+pm_is_noinvol_ancestor(pm_noinvol_t *aip)
+{
+ pm_noinvol_t *ip;
+
+ ASSERT(strlen(aip->ni_path) != 0);
+ for (ip = pm_noinvol_head; ip; ip = ip->ni_next) {
+ if (ip == aip)
+ continue;
+ /*
+ * To be an ancestor, the path must be an initial substring of
+ * the descendent, and end just before a '/' in the
+ * descendent's path.
+ */
+ if ((strstr(ip->ni_path, aip->ni_path) == ip->ni_path) &&
+ (ip->ni_path[strlen(aip->ni_path)] == '/'))
+ return (1);
+ }
+ return (0);
+}
+
+#define PM_MAJOR(dip) ddi_name_to_major(ddi_binding_name(dip))
+/*
+ * scan through the pm_noinvolpm list adjusting ancestors of the current
+ * node; Modifies string *path.
+ */
+static void
+adjust_ancestors(char *path, int wasvolpmd)
+{
+ PMD_FUNC(pmf, "adjust_ancestors")
+ char *cp;
+ pm_noinvol_t *lp;
+ pm_noinvol_t *pp = NULL;
+ major_t locked = (major_t)UINT_MAX;
+ dev_info_t *dip;
+ char *pathbuf;
+ size_t pathbuflen = strlen(path) + 1;
+
+ /*
+ * First we look up the ancestor's dip. If we find it, then we
+ * adjust counts up the tree
+ */
+ PMD(PMD_NOINVOL, ("%s: %s wasvolpmd %d\n", pmf, path, wasvolpmd))
+ pathbuf = kmem_alloc(pathbuflen, KM_SLEEP);
+ (void) strcpy(pathbuf, path);
+ cp = strrchr(pathbuf, '/');
+ if (cp == NULL) {
+ /* if no ancestors, then nothing to do */
+ kmem_free(pathbuf, pathbuflen);
+ return;
+ }
+ *cp = '\0';
+ dip = pm_name_to_dip(pathbuf, 1);
+ if (dip != NULL) {
+ locked = PM_MAJOR(dip);
+
+ (void) pm_noinvol_update(PM_BP_NOINVOL_REMDRV, 0, wasvolpmd,
+ path, dip);
+
+ if (locked != (major_t)UINT_MAX)
+ ddi_release_devi(dip);
+ } else {
+ char *apath;
+ size_t len = strlen(pathbuf) + 1;
+ int lock_held = 1;
+
+ /*
+ * Now check for ancestors that exist only in the list
+ */
+ apath = kmem_alloc(len, KM_SLEEP);
+ (void) strcpy(apath, pathbuf);
+ rw_enter(&pm_noinvol_rwlock, RW_WRITER);
+ for (lp = pm_noinvol_head; lp; pp = lp, lp = lp->ni_next) {
+ /*
+ * This can only happen once. Since we have to drop
+ * the lock, we need to extract the relevant info.
+ */
+ if (strcmp(pathbuf, lp->ni_path) == 0) {
+ PMD(PMD_NOINVOL, ("%s: %s no %d -> %d\n", pmf,
+ lp->ni_path, lp->ni_noinvolpm,
+ lp->ni_noinvolpm - 1))
+ lp->ni_noinvolpm--;
+ if (wasvolpmd && lp->ni_volpmd) {
+ PMD(PMD_NOINVOL, ("%s: %s vol %d -> "
+ "%d\n", pmf, lp->ni_path,
+ lp->ni_volpmd, lp->ni_volpmd - 1))
+ lp->ni_volpmd--;
+ }
+ /*
+ * remove the entry from the list, if there
+ * are no more no-invol descendants and node
+ * itself is not a no-invol node.
+ */
+ if (!(lp->ni_noinvolpm ||
+ (lp->ni_flags & PMC_NO_INVOL))) {
+ ASSERT(lp->ni_volpmd == 0);
+ if (pp) {
+ PMD(PMD_NOINVOL, ("%s: freeing "
+ "%s, prev is %s\n", pmf,
+ lp->ni_path, pp->ni_path))
+ pp->ni_next = lp->ni_next;
+ } else {
+ PMD(PMD_NOINVOL, ("%s: free %s "
+ "head\n", pmf, lp->ni_path))
+ ASSERT(pm_noinvol_head == lp);
+ pm_noinvol_head = lp->ni_next;
+ }
+ lock_held = 0;
+ rw_exit(&pm_noinvol_rwlock);
+ adjust_ancestors(apath, wasvolpmd);
+ /* restore apath */
+ (void) strcpy(apath, pathbuf);
+ kmem_free(lp->ni_path, lp->ni_size);
+ kmem_free(lp, sizeof (*lp));
+ }
+ break;
+ }
+ }
+ if (lock_held)
+ rw_exit(&pm_noinvol_rwlock);
+ adjust_ancestors(apath, wasvolpmd);
+ kmem_free(apath, len);
+ }
+ kmem_free(pathbuf, pathbuflen);
+}
+
+/*
+ * Do no-invol processing for any ancestors i.e. adjust counters of ancestors,
+ * which were skipped even though their drivers were removed.
+ */
+static void
+pm_noinvol_process_ancestors(char *path)
+{
+ pm_noinvol_t *lp;
+
+ rw_enter(&pm_noinvol_rwlock, RW_READER);
+ for (lp = pm_noinvol_head; lp; lp = lp->ni_next) {
+ if (strstr(path, lp->ni_path) &&
+ (lp->ni_flags & PMC_DRIVER_REMOVED)) {
+ rw_exit(&pm_noinvol_rwlock);
+ i_pm_driver_removed(lp->ni_major);
+ return;
+ }
+ }
+ rw_exit(&pm_noinvol_rwlock);
+}
+
+/*
+ * Returns true if (detached) device needs to be kept up because it exported the
+ * "no-involuntary-power-cycles" property or we're pretending it did (console
+ * fb case) or it is an ancestor of such a device and has used up the "one
+ * free cycle" allowed when all such leaf nodes have voluntarily powered down
+ * upon detach. In any event, we need an exact hit on the path or we return
+ * false.
+ */
+int
+pm_noinvol_detached(char *path)
+{
+ PMD_FUNC(pmf, "noinvol_detached")
+ pm_noinvol_t *ip;
+ int ret = 0;
+
+ rw_enter(&pm_noinvol_rwlock, RW_READER);
+ for (ip = pm_noinvol_head; ip; ip = ip->ni_next) {
+ if (strcmp(path, ip->ni_path) == 0) {
+ if (ip->ni_flags & PMC_CONSOLE_FB) {
+ PMD(PMD_NOINVOL | PMD_CFB, ("%s: inhibits CFB "
+ "%s\n", pmf, path))
+ ret = 1;
+ break;
+ }
+#ifdef DEBUG
+ if (ip->ni_noinvolpm != ip->ni_volpmd)
+ PMD(PMD_NOINVOL, ("%s: (%d != %d) inhibits %s"
+ "\n", pmf, ip->ni_noinvolpm, ip->ni_volpmd,
+ path))
+#endif
+ ret = (ip->ni_noinvolpm != ip->ni_volpmd);
+ break;
+ }
+ }
+ rw_exit(&pm_noinvol_rwlock);
+ return (ret);
+}
+
+int
+pm_is_cfb(dev_info_t *dip)
+{
+ return (dip == cfb_dip);
+}
+
+#ifdef DEBUG
+/*
+ * Return true if all components of the console frame buffer are at
+ * "normal" power, i.e., fully on. For the case where the console is not
+ * a framebuffer, we also return true
+ */
+int
+pm_cfb_is_up(void)
+{
+ return (pm_cfb_comps_off == 0);
+}
+#endif
+
+/*
+ * Preventing scan from powering down the node by incrementing the
+ * kidsupcnt.
+ */
+void
+pm_hold_power(dev_info_t *dip)
+{
+ e_pm_hold_rele_power(dip, 1);
+}
+
+/*
+ * Releasing the hold by decrementing the kidsupcnt allowing scan
+ * to power down the node if all conditions are met.
+ */
+void
+pm_rele_power(dev_info_t *dip)
+{
+ e_pm_hold_rele_power(dip, -1);
+}
+
+/*
+ * A wrapper of pm_all_to_normal() to power up a dip
+ * to its normal level
+ */
+int
+pm_powerup(dev_info_t *dip)
+{
+ PMD_FUNC(pmf, "pm_powerup")
+
+ PMD(PMD_ALLNORM, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ ASSERT(!(servicing_interrupt()));
+
+ /*
+ * in case this node is not already participating pm
+ */
+ if (!PM_GET_PM_INFO(dip)) {
+ if (!DEVI_IS_ATTACHING(dip))
+ return (DDI_SUCCESS);
+ if (pm_start(dip) != DDI_SUCCESS)
+ return (DDI_FAILURE);
+ if (!PM_GET_PM_INFO(dip))
+ return (DDI_SUCCESS);
+ }
+
+ return (pm_all_to_normal(dip, PM_CANBLOCK_BLOCK));
+}
+
+int
+pm_rescan_walk(dev_info_t *dip, void *arg)
+{
+ _NOTE(ARGUNUSED(arg))
+
+ if (!PM_GET_PM_INFO(dip) || PM_ISBC(dip))
+ return (DDI_WALK_CONTINUE);
+
+ /*
+ * Currently pm_cpr_callb/resume code is the only caller
+ * and it needs to make sure that stopped scan get
+ * reactivated. Otherwise, rescan walk needn't reactive
+ * stopped scan.
+ */
+ pm_scan_init(dip);
+
+ (void) pm_rescan(dip);
+ return (DDI_WALK_CONTINUE);
+}
+
+static dev_info_t *
+pm_get_next_descendent(dev_info_t *dip, dev_info_t *tdip)
+{
+ dev_info_t *wdip, *pdip;
+
+ for (wdip = tdip; wdip != dip; wdip = pdip) {
+ pdip = ddi_get_parent(wdip);
+ if (pdip == dip)
+ return (wdip);
+ }
+ return (NULL);
+}
+
+int
+pm_busop_bus_power(dev_info_t *dip, void *impl_arg, pm_bus_power_op_t op,
+ void *arg, void *result)
+{
+ PMD_FUNC(pmf, "bp_bus_power")
+ dev_info_t *cdip;
+ pm_info_t *cinfo;
+ pm_bp_child_pwrchg_t *bpc;
+ pm_sp_misc_t *pspm;
+ pm_bp_nexus_pwrup_t *bpn;
+ pm_bp_child_pwrchg_t new_bpc;
+ pm_bp_noinvol_t *bpi;
+ dev_info_t *tdip;
+ char *pathbuf;
+ int ret = DDI_SUCCESS;
+ int errno = 0;
+ pm_component_t *cp;
+
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d) %s\n", pmf, PM_DEVICE(dip),
+ pm_decode_op(op)))
+ switch (op) {
+ case BUS_POWER_CHILD_PWRCHG:
+ bpc = (pm_bp_child_pwrchg_t *)arg;
+ pspm = (pm_sp_misc_t *)bpc->bpc_private;
+ tdip = bpc->bpc_dip;
+ cdip = pm_get_next_descendent(dip, tdip);
+ cinfo = PM_GET_PM_INFO(cdip);
+ if (cdip != tdip) {
+ /*
+ * If the node is an involved parent, it needs to
+ * power up the node as it is needed. There is nothing
+ * else the framework can do here.
+ */
+ if (PM_WANTS_NOTIFICATION(cdip)) {
+ PMD(PMD_SET, ("%s: call bus_power for "
+ "%s@%s(%s#%d)\n", pmf, PM_DEVICE(cdip)))
+ return ((*PM_BUS_POWER_FUNC(cdip))(cdip,
+ impl_arg, op, arg, result));
+ }
+ ASSERT(pspm->pspm_direction == PM_LEVEL_UPONLY ||
+ pspm->pspm_direction == PM_LEVEL_DOWNONLY ||
+ pspm->pspm_direction == PM_LEVEL_EXACT);
+ /*
+ * we presume that the parent needs to be up in
+ * order for the child to change state (either
+ * because it must already be on if the child is on
+ * (and the pm_all_to_normal_nexus() will be a nop)
+ * or because it will need to be on for the child
+ * to come on; so we make the call regardless
+ */
+ pm_hold_power(cdip);
+ if (cinfo) {
+ pm_canblock_t canblock = pspm->pspm_canblock;
+ ret = pm_all_to_normal_nexus(cdip, canblock);
+ if (ret != DDI_SUCCESS) {
+ pm_rele_power(cdip);
+ return (ret);
+ }
+ }
+ PMD(PMD_SET, ("%s: walk down to %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(cdip)))
+ ret = pm_busop_bus_power(cdip, impl_arg, op, arg,
+ result);
+ pm_rele_power(cdip);
+ } else {
+ ret = pm_busop_set_power(cdip, impl_arg, op, arg,
+ result);
+ }
+ return (ret);
+
+ case BUS_POWER_NEXUS_PWRUP:
+ bpn = (pm_bp_nexus_pwrup_t *)arg;
+ pspm = (pm_sp_misc_t *)bpn->bpn_private;
+
+ if (!e_pm_valid_info(dip, NULL) ||
+ !e_pm_valid_comp(dip, bpn->bpn_comp, &cp) ||
+ !e_pm_valid_power(dip, bpn->bpn_comp, bpn->bpn_level)) {
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d) has no pm info; EIO\n",
+ pmf, PM_DEVICE(dip)))
+ *pspm->pspm_errnop = EIO;
+ *(int *)result = DDI_FAILURE;
+ return (DDI_FAILURE);
+ }
+
+ ASSERT(bpn->bpn_dip == dip);
+ PMD(PMD_SET, ("%s: nexus powerup for %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(dip)))
+ new_bpc.bpc_dip = dip;
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ new_bpc.bpc_path = ddi_pathname(dip, pathbuf);
+ new_bpc.bpc_comp = bpn->bpn_comp;
+ new_bpc.bpc_olevel = PM_CURPOWER(dip, bpn->bpn_comp);
+ new_bpc.bpc_nlevel = bpn->bpn_level;
+ new_bpc.bpc_private = bpn->bpn_private;
+ ((pm_sp_misc_t *)(new_bpc.bpc_private))->pspm_direction =
+ PM_LEVEL_UPONLY;
+ ((pm_sp_misc_t *)(new_bpc.bpc_private))->pspm_errnop =
+ &errno;
+ ret = pm_busop_set_power(dip, impl_arg, BUS_POWER_CHILD_PWRCHG,
+ (void *)&new_bpc, result);
+ kmem_free(pathbuf, MAXPATHLEN);
+ return (ret);
+
+ case BUS_POWER_NOINVOL:
+ bpi = (pm_bp_noinvol_t *)arg;
+ tdip = bpi->bpni_dip;
+ cdip = pm_get_next_descendent(dip, tdip);
+
+ /* In case of rem_drv, the leaf node has been removed */
+ if (cdip == NULL)
+ return (DDI_SUCCESS);
+
+ cinfo = PM_GET_PM_INFO(cdip);
+ if (cdip != tdip) {
+ if (PM_WANTS_NOTIFICATION(cdip)) {
+ PMD(PMD_NOINVOL,
+ ("%s: call bus_power for %s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(cdip)))
+ ret = (*PM_BUS_POWER_FUNC(cdip))
+ (cdip, NULL, op, arg, result);
+ if ((cinfo) && (ret == DDI_SUCCESS))
+ (void) pm_noinvol_update_node(cdip,
+ bpi);
+ return (ret);
+ } else {
+ PMD(PMD_NOINVOL,
+ ("%s: walk down to %s@%s(%s#%d)\n", pmf,
+ PM_DEVICE(cdip)))
+ ret = pm_busop_bus_power(cdip, NULL, op,
+ arg, result);
+ /*
+ * Update the current node.
+ */
+ if ((cinfo) && (ret == DDI_SUCCESS))
+ (void) pm_noinvol_update_node(cdip,
+ bpi);
+ return (ret);
+ }
+ } else {
+ /*
+ * For attach, detach, power up:
+ * Do nothing for leaf node since its
+ * counts are already updated.
+ * For CFB and driver removal, since the
+ * path and the target dip passed in is up to and incl.
+ * the immediate ancestor, need to do the update.
+ */
+ PMD(PMD_NOINVOL, ("%s: target %s@%s(%s#%d) is "
+ "reached\n", pmf, PM_DEVICE(cdip)))
+ if (cinfo && ((bpi->bpni_cmd == PM_BP_NOINVOL_REMDRV) ||
+ (bpi->bpni_cmd == PM_BP_NOINVOL_CFB)))
+ (void) pm_noinvol_update_node(cdip, bpi);
+ return (DDI_SUCCESS);
+ }
+
+ default:
+ PMD(PMD_SET, ("%s: operation %d is not supported!\n", pmf, op))
+ return (DDI_FAILURE);
+ }
+}
+
+static int
+pm_busop_set_power(dev_info_t *dip, void *impl_arg, pm_bus_power_op_t op,
+ void *arg, void *resultp)
+{
+ _NOTE(ARGUNUSED(impl_arg))
+ PMD_FUNC(pmf, "bp_set_power")
+ pm_ppm_devlist_t *devl;
+ int clevel, circ;
+#ifdef DEBUG
+ int circ_db, ccirc_db;
+#endif
+ int ret = DDI_SUCCESS;
+ dev_info_t *cdip;
+ pm_bp_child_pwrchg_t *bpc = (pm_bp_child_pwrchg_t *)arg;
+ pm_sp_misc_t *pspm = (pm_sp_misc_t *)bpc->bpc_private;
+ pm_canblock_t canblock = pspm->pspm_canblock;
+ int scan = pspm->pspm_scan;
+ int comp = bpc->bpc_comp;
+ int olevel = bpc->bpc_olevel;
+ int nlevel = bpc->bpc_nlevel;
+ int comps_off_incr = 0;
+ dev_info_t *pdip = ddi_get_parent(dip);
+ int dodeps;
+ int direction = pspm->pspm_direction;
+ int *errnop = pspm->pspm_errnop;
+ char *dir = pm_decode_direction(direction);
+ int *iresp = (int *)resultp;
+ time_t idletime, thresh;
+ pm_component_t *cp = PM_CP(dip, comp);
+ int work_type;
+
+ *iresp = DDI_SUCCESS;
+ *errnop = 0;
+ ASSERT(op == BUS_POWER_CHILD_PWRCHG);
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d) %s\n", pmf, PM_DEVICE(dip),
+ pm_decode_op(op)))
+
+ /*
+ * The following set of conditions indicate we are here to handle a
+ * driver's pm_[raise|lower]_power request, but the device is being
+ * power managed (PM_DIRECT_PM) by a user process. For that case
+ * we want to pm_block and pass a status back to the caller based
+ * on whether the controlling process's next activity on the device
+ * matches the current request or not. This distinction tells
+ * downstream functions to avoid calling into a driver or changing
+ * the framework's power state. To actually block, we need:
+ *
+ * PM_ISDIRECT(dip)
+ * no reason to block unless a process is directly controlling dev
+ * direction != PM_LEVEL_EXACT
+ * EXACT is used by controlling proc's PM_SET_CURRENT_POWER ioctl
+ * !pm_processes_stopped
+ * don't block if controlling proc already be stopped for cpr
+ * canblock != PM_CANBLOCK_BYPASS
+ * our caller must not have explicitly prevented blocking
+ */
+ if (direction != PM_LEVEL_EXACT && canblock != PM_CANBLOCK_BYPASS) {
+ PM_LOCK_DIP(dip);
+ while (PM_ISDIRECT(dip) && !pm_processes_stopped) {
+ /* releases dip lock */
+ ret = pm_busop_match_request(dip, bpc);
+ if (ret == EAGAIN) {
+ PM_LOCK_DIP(dip);
+ continue;
+ }
+ return (*iresp = ret);
+ }
+ PM_UNLOCK_DIP(dip);
+ }
+ /* BC device is never scanned, so power will stick until we are done */
+ if (PM_ISBC(dip) && comp != 0 && nlevel != 0 &&
+ direction != PM_LEVEL_DOWNONLY) {
+ int nrmpwr0 = pm_get_normal_power(dip, 0);
+ if (pm_set_power(dip, 0, nrmpwr0, direction,
+ canblock, 0, resultp) != DDI_SUCCESS) {
+ /* *resultp set by pm_set_power */
+ return (DDI_FAILURE);
+ }
+ }
+ if (PM_WANTS_NOTIFICATION(pdip)) {
+ PMD(PMD_SET, ("%s: pre_notify %s@%s(%s#%d) for child "
+ "%s@%s(%s#%d)\n", pmf, PM_DEVICE(pdip), PM_DEVICE(dip)))
+ ret = (*PM_BUS_POWER_FUNC(pdip))(pdip, NULL,
+ BUS_POWER_PRE_NOTIFICATION, bpc, resultp);
+ if (ret != DDI_SUCCESS) {
+ PMD(PMD_SET, ("%s: failed to pre_notify %s@%s(%s#%d)\n",
+ pmf, PM_DEVICE(pdip)))
+ return (DDI_FAILURE);
+ }
+ } else {
+ /*
+ * Since we don't know what the actual power level is,
+ * we place a power hold on the parent no matter what
+ * component and level is changing.
+ */
+ pm_hold_power(pdip);
+ }
+ PM_LOCK_POWER(dip, &circ);
+ clevel = PM_CURPOWER(dip, comp);
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d), cmp=%d, olvl=%d, nlvl=%d, clvl=%d, "
+ "dir=%s\n", pmf, PM_DEVICE(dip), comp, bpc->bpc_olevel, nlevel,
+ clevel, dir))
+ switch (direction) {
+ case PM_LEVEL_UPONLY:
+ /* Powering up */
+ if (clevel >= nlevel) {
+ PMD(PMD_SET, ("%s: current level is already "
+ "at or above the requested level.\n", pmf))
+ *iresp = DDI_SUCCESS;
+ ret = DDI_SUCCESS;
+ goto post_notify;
+ }
+ break;
+ case PM_LEVEL_EXACT:
+ /* specific level request */
+ if (clevel == nlevel && !PM_ISBC(dip)) {
+ PMD(PMD_SET, ("%s: current level is already "
+ "at the requested level.\n", pmf))
+ *iresp = DDI_SUCCESS;
+ ret = DDI_SUCCESS;
+ goto post_notify;
+ } else if (PM_IS_CFB(dip) && (nlevel < clevel)) {
+ PMD(PMD_CFB, ("%s: powerdown of console\n", pmf))
+ if (!pm_cfb_enabled) {
+ PMD(PMD_ERROR | PMD_CFB,
+ ("%s: !pm_cfb_enabled, fails\n", pmf))
+ *errnop = EINVAL;
+ *iresp = DDI_FAILURE;
+ ret = DDI_FAILURE;
+ goto post_notify;
+ }
+ mutex_enter(&pm_cfb_lock);
+ while (cfb_inuse) {
+ mutex_exit(&pm_cfb_lock);
+ if (delay_sig(1) == EINTR) {
+ ret = DDI_FAILURE;
+ *iresp = DDI_FAILURE;
+ *errnop = EINTR;
+ goto post_notify;
+ }
+ mutex_enter(&pm_cfb_lock);
+ }
+ mutex_exit(&pm_cfb_lock);
+ }
+ break;
+ case PM_LEVEL_DOWNONLY:
+ /* Powering down */
+ thresh = cur_threshold(dip, comp);
+ idletime = gethrestime_sec() - cp->pmc_timestamp;
+ if (scan && ((PM_KUC(dip) != 0) ||
+ (cp->pmc_busycount > 0) || (idletime < thresh))) {
+#ifdef DEBUG
+ if (DEVI(dip)->devi_pm_kidsupcnt != 0)
+ PMD(PMD_SET, ("%s: scan failed: "
+ "kidsupcnt != 0\n", pmf))
+ if (cp->pmc_busycount > 0)
+ PMD(PMD_SET, ("%s: scan failed: "
+ "device become busy\n", pmf))
+ if (idletime < thresh)
+ PMD(PMD_SET, ("%s: scan failed: device "
+ "hasn't been idle long enough\n", pmf))
+#endif
+ *iresp = DDI_FAILURE;
+ *errnop = EBUSY;
+ ret = DDI_FAILURE;
+ goto post_notify;
+ } else if (clevel != PM_LEVEL_UNKNOWN && clevel <= nlevel) {
+ PMD(PMD_SET, ("%s: current level is already at "
+ "or below the requested level.\n", pmf))
+ *iresp = DDI_SUCCESS;
+ ret = DDI_SUCCESS;
+ goto post_notify;
+ }
+ break;
+ }
+
+ if (PM_IS_CFB(dip) && (comps_off_incr =
+ calc_cfb_comps_incr(dip, comp, clevel, nlevel)) > 0) {
+ /*
+ * Pre-adjust pm_cfb_comps_off if lowering a console fb
+ * component from full power. Remember that we tried to
+ * lower power in case it fails and we need to back out
+ * the adjustment.
+ */
+ update_comps_off(comps_off_incr, dip);
+ PMD(PMD_CFB, ("%s: %s@%s(%s#%d)[%d] %d->%d cfb_comps_off->%d\n",
+ pmf, PM_DEVICE(dip), comp, clevel, nlevel,
+ pm_cfb_comps_off))
+ }
+
+ if ((*iresp = power_dev(dip,
+ comp, nlevel, clevel, canblock, &devl)) == DDI_SUCCESS) {
+#ifdef DEBUG
+ /*
+ * All descendents of this node should already be powered off.
+ */
+ if (PM_CURPOWER(dip, comp) == 0) {
+ pm_desc_pwrchk_t pdpchk;
+ pdpchk.pdpc_dip = dip;
+ pdpchk.pdpc_par_involved = PM_WANTS_NOTIFICATION(dip);
+ ndi_devi_enter(dip, &circ_db);
+ for (cdip = ddi_get_child(dip); cdip != NULL;
+ cdip = ddi_get_next_sibling(cdip)) {
+ ndi_devi_enter(cdip, &ccirc_db);
+ ddi_walk_devs(cdip, pm_desc_pwrchk_walk,
+ (void *)&pdpchk);
+ ndi_devi_exit(cdip, ccirc_db);
+ }
+ ndi_devi_exit(dip, circ_db);
+ }
+#endif
+ /*
+ * Post-adjust pm_cfb_comps_off if we brought an fb component
+ * back up to full power.
+ */
+ if (PM_IS_CFB(dip) && comps_off_incr < 0) {
+ update_comps_off(comps_off_incr, dip);
+ PMD(PMD_CFB, ("%s: %s@%s(%s#%d)[%d] %d->%d "
+ "cfb_comps_off->%d\n", pmf, PM_DEVICE(dip),
+ comp, clevel, nlevel, pm_cfb_comps_off))
+ }
+ dodeps = 0;
+ if (POWERING_OFF(clevel, nlevel)) {
+ if (PM_ISBC(dip)) {
+ dodeps = (comp == 0);
+ } else {
+ int i;
+ dodeps = 1;
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ /* if some component still on */
+ if (PM_CURPOWER(dip, i)) {
+ dodeps = 0;
+ break;
+ }
+ }
+ }
+ if (dodeps)
+ work_type = PM_DEP_WK_POWER_OFF;
+ } else if (POWERING_ON(clevel, nlevel)) {
+ if (PM_ISBC(dip)) {
+ dodeps = (comp == 0);
+ } else {
+ int i;
+ dodeps = 1;
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ if (i == comp)
+ continue;
+ if (PM_CURPOWER(dip, i) > 0) {
+ dodeps = 0;
+ break;
+ }
+ }
+ }
+ if (dodeps)
+ work_type = PM_DEP_WK_POWER_ON;
+ }
+
+ if (dodeps) {
+ char *pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+
+ (void) ddi_pathname(dip, pathbuf);
+ pm_dispatch_to_dep_thread(work_type, pathbuf, NULL,
+ PM_DEP_NOWAIT, NULL, 0);
+ kmem_free(pathbuf, MAXPATHLEN);
+ }
+ if ((PM_CURPOWER(dip, comp) == nlevel) && pm_watchers()) {
+ int old;
+
+ /* If old power cached during deadlock, use it. */
+ old = (cp->pmc_flags & PM_PHC_WHILE_SET_POWER ?
+ cp->pmc_phc_pwr : olevel);
+ mutex_enter(&pm_rsvp_lock);
+ pm_enqueue_notify(PSC_HAS_CHANGED, dip, comp, nlevel,
+ old, canblock);
+ pm_enqueue_notify_others(&devl, canblock);
+ mutex_exit(&pm_rsvp_lock);
+ }
+
+ /*
+ * If we are coming from a scan, don't do it again,
+ * else we can have infinite loops.
+ */
+ if (!scan)
+ pm_rescan(dip);
+ } else {
+ /* if we incremented pm_comps_off_count, but failed */
+ if (comps_off_incr > 0) {
+ update_comps_off(-comps_off_incr, dip);
+ PMD(PMD_CFB, ("%s: %s@%s(%s#%d)[%d] %d->%d "
+ "cfb_comps_off->%d\n", pmf, PM_DEVICE(dip),
+ comp, clevel, nlevel, pm_cfb_comps_off))
+ }
+ *errnop = EIO;
+ }
+
+post_notify:
+ /*
+ * This thread may have been in deadlock with pm_power_has_changed.
+ * Before releasing power lock, clear the flag which marks this
+ * condition.
+ */
+ cp->pmc_flags &= ~PM_PHC_WHILE_SET_POWER;
+
+ /*
+ * Update the old power level in the bus power structure with the
+ * actual power level before the transition was made to the new level.
+ * Some involved parents depend on this information to keep track of
+ * their children's power transition.
+ */
+ if (*iresp != DDI_FAILURE)
+ bpc->bpc_olevel = clevel;
+
+ if (PM_WANTS_NOTIFICATION(pdip)) {
+ ret = (*PM_BUS_POWER_FUNC(pdip))(pdip, NULL,
+ BUS_POWER_POST_NOTIFICATION, bpc, resultp);
+ PM_UNLOCK_POWER(dip, circ);
+ PMD(PMD_SET, ("%s: post_notify %s@%s(%s#%d) for "
+ "child %s@%s(%s#%d), ret=%d\n", pmf, PM_DEVICE(pdip),
+ PM_DEVICE(dip), ret))
+ } else {
+ nlevel = cur_power(cp); /* in case phc deadlock updated pwr */
+ PM_UNLOCK_POWER(dip, circ);
+ /*
+ * Now that we know what power transition has occurred
+ * (if any), release the power hold. Leave the hold
+ * in effect in the case of OFF->ON transition.
+ */
+ if (!(clevel == 0 && nlevel > 0 &&
+ (!PM_ISBC(dip) || comp == 0)))
+ pm_rele_power(pdip);
+ /*
+ * If the power transition was an ON->OFF transition,
+ * remove the power hold from the parent.
+ */
+ if ((clevel > 0 || clevel == PM_LEVEL_UNKNOWN) &&
+ nlevel == 0 && (!PM_ISBC(dip) || comp == 0))
+ pm_rele_power(pdip);
+ }
+ if (*iresp != DDI_SUCCESS || ret != DDI_SUCCESS)
+ return (DDI_FAILURE);
+ else
+ return (DDI_SUCCESS);
+}
+
+/*
+ * If an app (SunVTS or Xsun) has taken control, then block until it
+ * gives it up or makes the requested power level change, unless
+ * we have other instructions about blocking. Returns DDI_SUCCESS,
+ * DDI_FAILURE or EAGAIN (owner released device from directpm).
+ */
+static int
+pm_busop_match_request(dev_info_t *dip, void *arg)
+{
+ PMD_FUNC(pmf, "bp_match_request")
+ pm_bp_child_pwrchg_t *bpc = (pm_bp_child_pwrchg_t *)arg;
+ pm_sp_misc_t *pspm = (pm_sp_misc_t *)bpc->bpc_private;
+ int comp = bpc->bpc_comp;
+ int nlevel = bpc->bpc_nlevel;
+ pm_canblock_t canblock = pspm->pspm_canblock;
+ int direction = pspm->pspm_direction;
+ int clevel, circ;
+
+ ASSERT(PM_IAM_LOCKING_DIP(dip));
+ PM_LOCK_POWER(dip, &circ);
+ clevel = PM_CURPOWER(dip, comp);
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d), cmp=%d, nlvl=%d, clvl=%d\n",
+ pmf, PM_DEVICE(dip), comp, nlevel, clevel))
+ if (direction == PM_LEVEL_UPONLY) {
+ if (clevel >= nlevel) {
+ PM_UNLOCK_POWER(dip, circ);
+ PM_UNLOCK_DIP(dip);
+ return (DDI_SUCCESS);
+ }
+ } else if (clevel == nlevel) {
+ PM_UNLOCK_POWER(dip, circ);
+ PM_UNLOCK_DIP(dip);
+ return (DDI_SUCCESS);
+ }
+ if (canblock == PM_CANBLOCK_FAIL) {
+ PM_UNLOCK_POWER(dip, circ);
+ PM_UNLOCK_DIP(dip);
+ return (DDI_FAILURE);
+ }
+ if (canblock == PM_CANBLOCK_BLOCK) {
+ /*
+ * To avoid a deadlock, we must not hold the
+ * power lock when we pm_block.
+ */
+ PM_UNLOCK_POWER(dip, circ);
+ PMD(PMD_SET, ("%s: blocking\n", pmf))
+ /* pm_block releases dip lock */
+ switch (pm_block(dip, comp, nlevel, clevel)) {
+ case PMP_RELEASE:
+ return (EAGAIN);
+ case PMP_SUCCEED:
+ return (DDI_SUCCESS);
+ case PMP_FAIL:
+ return (DDI_FAILURE);
+ }
+ } else {
+ ASSERT(0);
+ }
+ _NOTE(NOTREACHED);
+ return (DDI_FAILURE); /* keep gcc happy */
+}
+
+static int
+pm_all_to_normal_nexus(dev_info_t *dip, pm_canblock_t canblock)
+{
+ PMD_FUNC(pmf, "all_to_normal_nexus")
+ int *normal;
+ int i, ncomps;
+ size_t size;
+ int changefailed = 0;
+ int ret, result = DDI_SUCCESS;
+ pm_bp_nexus_pwrup_t bpn;
+ pm_sp_misc_t pspm;
+
+ ASSERT(PM_GET_PM_INFO(dip));
+ PMD(PMD_ALLNORM, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ if (pm_get_norm_pwrs(dip, &normal, &size) != DDI_SUCCESS) {
+ PMD(PMD_ALLNORM, ("%s: can't get norm pwrs\n", pmf))
+ return (DDI_FAILURE);
+ }
+ ncomps = PM_NUMCMPTS(dip);
+ for (i = 0; i < ncomps; i++) {
+ bpn.bpn_dip = dip;
+ bpn.bpn_comp = i;
+ bpn.bpn_level = normal[i];
+ pspm.pspm_canblock = canblock;
+ pspm.pspm_scan = 0;
+ bpn.bpn_private = &pspm;
+ ret = pm_busop_bus_power(dip, NULL, BUS_POWER_NEXUS_PWRUP,
+ (void *)&bpn, (void *)&result);
+ if (ret != DDI_SUCCESS || result != DDI_SUCCESS) {
+ PMD(PMD_FAIL | PMD_ALLNORM, ("%s: %s@%s(%s#%d)[%d] "
+ "->%d failure result %d\n", pmf, PM_DEVICE(dip),
+ i, normal[i], result))
+ changefailed++;
+ }
+ }
+ kmem_free(normal, size);
+ if (changefailed) {
+ PMD(PMD_FAIL, ("%s: failed to set %d comps %s@%s(%s#%d) "
+ "full power\n", pmf, changefailed, PM_DEVICE(dip)))
+ return (DDI_FAILURE);
+ }
+ return (DDI_SUCCESS);
+}
+
+int
+pm_noinvol_update(int subcmd, int volpmd, int wasvolpmd, char *path,
+ dev_info_t *tdip)
+{
+ PMD_FUNC(pmf, "noinvol_update")
+ pm_bp_noinvol_t args;
+ int ret;
+ int result = DDI_SUCCESS;
+
+ args.bpni_path = path;
+ args.bpni_dip = tdip;
+ args.bpni_cmd = subcmd;
+ args.bpni_wasvolpmd = wasvolpmd;
+ args.bpni_volpmd = volpmd;
+ PMD(PMD_NOINVOL, ("%s: update for path %s tdip %p subcmd %d "
+ "volpmd %d wasvolpmd %d\n", pmf,
+ path, (void *)tdip, subcmd, wasvolpmd, volpmd))
+ ret = pm_busop_bus_power(ddi_root_node(), NULL, BUS_POWER_NOINVOL,
+ &args, &result);
+ return (ret);
+}
+
+void
+pm_noinvol_update_node(dev_info_t *dip, pm_bp_noinvol_t *req)
+{
+ PMD_FUNC(pmf, "noinvol_update_node")
+
+ PMD(PMD_NOINVOL, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ switch (req->bpni_cmd) {
+ case PM_BP_NOINVOL_ATTACH:
+ PMD(PMD_NOINVOL, ("%s: PM_PB_NOINVOL_ATTACH %s@%s(%s#%d) "
+ "noinvol %d->%d\n", pmf, PM_DEVICE(dip),
+ DEVI(dip)->devi_pm_noinvolpm,
+ DEVI(dip)->devi_pm_noinvolpm - 1))
+ ASSERT(DEVI(dip)->devi_pm_noinvolpm);
+ PM_LOCK_DIP(dip);
+ DEVI(dip)->devi_pm_noinvolpm--;
+ if (req->bpni_wasvolpmd) {
+ PMD(PMD_NOINVOL, ("%s: PM_BP_NOINVOL_ATTACH "
+ "%s@%s(%s#%d) volpmd %d->%d\n", pmf,
+ PM_DEVICE(dip), DEVI(dip)->devi_pm_volpmd,
+ DEVI(dip)->devi_pm_volpmd - 1))
+ if (DEVI(dip)->devi_pm_volpmd)
+ DEVI(dip)->devi_pm_volpmd--;
+ }
+ PM_UNLOCK_DIP(dip);
+ break;
+
+ case PM_BP_NOINVOL_DETACH:
+ PMD(PMD_NOINVOL, ("%s: PM_BP_NOINVOL_DETACH %s@%s(%s#%d) "
+ "noinvolpm %d->%d\n", pmf, PM_DEVICE(dip),
+ DEVI(dip)->devi_pm_noinvolpm,
+ DEVI(dip)->devi_pm_noinvolpm + 1))
+ PM_LOCK_DIP(dip);
+ DEVI(dip)->devi_pm_noinvolpm++;
+ if (req->bpni_wasvolpmd) {
+ PMD(PMD_NOINVOL, ("%s: PM_BP_NOINVOL_DETACH "
+ "%s@%s(%s#%d) volpmd %d->%d\n", pmf,
+ PM_DEVICE(dip), DEVI(dip)->devi_pm_volpmd,
+ DEVI(dip)->devi_pm_volpmd + 1))
+ DEVI(dip)->devi_pm_volpmd++;
+ }
+ PM_UNLOCK_DIP(dip);
+ break;
+
+ case PM_BP_NOINVOL_REMDRV:
+ PMD(PMD_NOINVOL, ("%s: PM_BP_NOINVOL_REMDRV %s@%s(%s#%d) "
+ "noinvol %d->%d\n", pmf, PM_DEVICE(dip),
+ DEVI(dip)->devi_pm_noinvolpm,
+ DEVI(dip)->devi_pm_noinvolpm - 1))
+ ASSERT(DEVI(dip)->devi_pm_noinvolpm);
+ PM_LOCK_DIP(dip);
+ DEVI(dip)->devi_pm_noinvolpm--;
+ if (req->bpni_wasvolpmd) {
+ PMD(PMD_NOINVOL,
+ ("%s: PM_BP_NOINVOL_REMDRV %s@%s(%s#%d) "
+ "volpmd %d->%d\n", pmf, PM_DEVICE(dip),
+ DEVI(dip)->devi_pm_volpmd,
+ DEVI(dip)->devi_pm_volpmd - 1))
+ /*
+ * A power up could come in between and
+ * clear the volpmd, if that's the case,
+ * volpmd would be clear.
+ */
+ if (DEVI(dip)->devi_pm_volpmd)
+ DEVI(dip)->devi_pm_volpmd--;
+ }
+ PM_UNLOCK_DIP(dip);
+ break;
+
+ case PM_BP_NOINVOL_CFB:
+ PMD(PMD_NOINVOL,
+ ("%s: PM_BP_NOIVOL_CFB %s@%s(%s#%d) noinvol %d->%d\n",
+ pmf, PM_DEVICE(dip), DEVI(dip)->devi_pm_noinvolpm,
+ DEVI(dip)->devi_pm_noinvolpm + 1))
+ PM_LOCK_DIP(dip);
+ DEVI(dip)->devi_pm_noinvolpm++;
+ PM_UNLOCK_DIP(dip);
+ break;
+
+ case PM_BP_NOINVOL_POWER:
+ PMD(PMD_NOINVOL,
+ ("%s: PM_BP_NOIVOL_PWR %s@%s(%s#%d) volpmd %d->%d\n",
+ pmf, PM_DEVICE(dip),
+ DEVI(dip)->devi_pm_volpmd, DEVI(dip)->devi_pm_volpmd -
+ req->bpni_volpmd))
+ PM_LOCK_DIP(dip);
+ DEVI(dip)->devi_pm_volpmd -= req->bpni_volpmd;
+ PM_UNLOCK_DIP(dip);
+ break;
+
+ default:
+ break;
+ }
+
+}
+
+#ifdef DEBUG
+static int
+pm_desc_pwrchk_walk(dev_info_t *dip, void *arg)
+{
+ PMD_FUNC(pmf, "desc_pwrchk")
+ pm_desc_pwrchk_t *pdpchk = (pm_desc_pwrchk_t *)arg;
+ pm_info_t *info = PM_GET_PM_INFO(dip);
+ int i, curpwr, ce_level;
+
+ if (!info)
+ return (DDI_WALK_CONTINUE);
+
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d)\n", pmf, PM_DEVICE(dip)))
+ for (i = 0; i < PM_NUMCMPTS(dip); i++) {
+ if ((curpwr = PM_CURPOWER(dip, i)) == 0)
+ continue;
+ ce_level = (pdpchk->pdpc_par_involved == 0) ? CE_PANIC :
+ CE_WARN;
+ PMD(PMD_SET, ("%s: %s@%s(%s#%d) is powered off while desc "
+ "%s@%s(%s#%d)[%d] is at %d\n", pmf,
+ PM_DEVICE(pdpchk->pdpc_dip), PM_DEVICE(dip), i, curpwr))
+ cmn_err(ce_level, "!device %s@%s(%s#%d) is powered on, "
+ "while its ancestor, %s@%s(%s#%d), is powering off!",
+ PM_DEVICE(dip), PM_DEVICE(pdpchk->pdpc_dip));
+ }
+ return (DDI_WALK_CONTINUE);
+}
+#endif
+
+/*
+ * Record the fact that one thread is borrowing the lock on a device node.
+ * Use is restricted to the case where the lending thread will block until
+ * the borrowing thread (always curthread) completes.
+ */
+void
+pm_borrow_lock(kthread_t *lender)
+{
+ lock_loan_t *prev = &lock_loan_head;
+ lock_loan_t *cur = (lock_loan_t *)kmem_zalloc(sizeof (*cur), KM_SLEEP);
+
+ cur->pmlk_borrower = curthread;
+ cur->pmlk_lender = lender;
+ mutex_enter(&pm_loan_lock);
+ cur->pmlk_next = prev->pmlk_next;
+ prev->pmlk_next = cur;
+ mutex_exit(&pm_loan_lock);
+}
+
+/*
+ * Return the borrowed lock. A thread can borrow only one.
+ */
+void
+pm_return_lock(void)
+{
+ lock_loan_t *cur;
+ lock_loan_t *prev = &lock_loan_head;
+
+ mutex_enter(&pm_loan_lock);
+ ASSERT(prev->pmlk_next != NULL);
+ for (cur = prev->pmlk_next; cur; prev = cur, cur = cur->pmlk_next)
+ if (cur->pmlk_borrower == curthread)
+ break;
+
+ ASSERT(cur != NULL);
+ prev->pmlk_next = cur->pmlk_next;
+ mutex_exit(&pm_loan_lock);
+ kmem_free(cur, sizeof (*cur));
+}