?? dpm.c
字號:
/* * drivers/dpm/policy.c Dynamic Power Management Policies * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Copyright (C) 2002, International Business Machines Corporation * All Rights Reserved * * Robert Paulsen * IBM Linux Technology Center * rpaulsen@us.ibm.com * August, 2002 * *//* TODO: Rethink init/enable/disable: It may be redundant and/or unsafe Fix initialization and stats*/#include <linux/dpm.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/module.h>#include <linux/proc_fs.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/trace.h>#include <asm/semaphore.h>#include <asm/system.h>#include <asm/uaccess.h>// debug printout// #define TRACE 1#undef TRACE#if defined(TRACE)#define trace(args...) do { printk("TRACE: "); printk(args); } while(0)#else#define trace(args...) do {} while(0)#endif#define DPM_PP_SIZE (DPM_PP_NBR * sizeof(dpm_md_pp_t))/****************************************************************************DPM Synchronization and Operating Point Changes===============================================There are 2 aspects to synchronization in DPM: First, the usual requirement ofserializing access to shared data structures, and second, the idea ofsynchronizing the operating point and the current operating state. The secondcondition arises because setting an operating point may complete asynchronouslyfor a number of reasons, whereas the operating state change that causes theoperating point change succeeds immediately.Access to most of the global variables representing the current state of DPMand the current policy are protected by a spinlock, dpm_policy_lock. The useof this lock appears in only a few critical places.Setting the operating point, reading the value of the current operating pointor changing the current policy may only be done while holding the semaphoredpm_sem. Access to the dpm_sem is abstracted by the dpm_lock() anddpm_unlock() calls as explained below. (The semaphore should only be accessedthis way to simplify future development).The dpm_sem must be held (by a call to a dpm_lock function) by any caller ofthe interfaces that set the operating point, change the policy, or enable ordisable DPM. Note that the corresponding call to dpm_unlock() may beexplicitly required, or implicit (see dpm_set_opt_async() below).For simplicity, the calls that create operating points and policies also usedpm_lock() and dpm_unlock() to protect access to the non-active policies aswell. Since these are normally initialization calls, this should not interferewith the operation of the system once initialized.Three interfaces are provided for obtaining the dpm_sem:void dpm_lock();int dpm_lock_interruptible();int dpm_trylock();dpm_lock_interruptible() returns -ERESTARTSYS if the wait for the dpm_sem wasinterrupted, and dpm_trylock() returns -EBUSY if the semaphore is currentlyheld. Once the dpm_sem is held, two interfaces are provided for setting theoperating point:int dpm_set_opt_async()int dpm_set_opt_sync();Neither of these interfaces takes parameters since under DPM the operatingpoint to select is always implied by the current policy and operating state.If the system is already at the correct operating point then no change isrequired or made. To avoid deadlock, the caller must not be holding thedpm_policy_lock when either of these calls is made.dpm_set_opt_async() launches a change in the operating point that willpotentially terminate asynchronously. This interface never blocks the caller,thus there is no guarantee that the system is actually running at the impliedoperating point when control returns to the caller. This call is used bydpm_set_os() during an operating state change. Note since this call terminatesasynchronously, the call to dpm_unlock() is implicitly made when the operatingpoint change is complete. I.e., the caller obtains the dpm_sem withdpm_lock(), calls dpm_set_opt_async(), then continues.dpm_set_opt_sync() launches a synchronous change in the operating point. Thiscall will block the caller as necessary during the call, thus it can only beissued from a process context. When control returns to the caller, the callercan be sure that the implied operating point was set, and that the system iscurrently running at the correct operating point for the given policy andoperating state. This call is used by dpm_set_policy() and the deviceconstraint update code to guarantee that the change to a new policy, or changesto operating point classes as a result of device constraits are reflected inthe operating point.Note that regardless of whether an operating point change is synchrounous orasynchronous, it is still possible that the operating state may change duringthe call. Setting the operating point is (currently) not preemptible,therefore at the time that the operating point change is complete, it may nolonger be the correct operating point for the operating state. This conditionis always handled by the dpm_set_opt*() routines, which will launch a taskletto re-synchronize the operating point to the operating state.It is possible that due to poorly designed policies and asynchronoustermination of operating point changes that the operating point will always lagbehind the operating state. This is only a performance issue, not acorrectness issue. Since a valid policy has a valid operating point for everyoperating state, and changes to the policy and changes in devices constraintsalways use dpm_set_opt_sync(), there will never be a case where the currentoperating point does not support device constraints.****************************************************************************//* curently installed policies, classes and operating points */LIST_HEAD(dpm_policies);LIST_HEAD(dpm_classes);LIST_HEAD(dpm_opts);DECLARE_MUTEX(dpm_sem);spinlock_t dpm_policy_lock = SPIN_LOCK_UNLOCKED;/* the currently active policy */struct dpm_policy *dpm_active_policy = 0;/* the currently active operating state and operating point */dpm_state_t dpm_active_state = DPM_NO_STATE;struct dpm_opt *dpm_active_opt = 0;/* is DPM initialized and enabled? */int dpm_enabled = 0;int dpm_initialized = 0;/***************************************************************************** * dpm_next_opt() returns the operating point that needs to be activated next, * or NULL if the operating point is up-to-date or the DPM system is disabled. * Since this call looks at the value of the current operating point, it can * only be made when the dpm_sem is held. *****************************************************************************/static inline struct dpm_opt *dpm_next_opt(void){ struct dpm_opt *opt = NULL; unsigned long flags; spin_lock_irqsave(&dpm_policy_lock, flags); if (dpm_enabled) { opt = dpm_active_policy->classes[dpm_active_state]->opt; if (opt == dpm_active_opt) opt = NULL; } spin_unlock_irqrestore(&dpm_policy_lock, flags); return opt;}/***************************************************************************** * Set the operating point implied by the current DPM policy. These calls can * only be made while holding dpm_sem, and the release of * dpm_sem is implied by the call (see below). *****************************************************************************//***************************************************************************** * Set operating point asynchronously. The dpm_sem will be cleared whenever * the change in operating point is complete. *****************************************************************************/intdpm_set_opt_async(void){ struct dpm_opt *opt = dpm_next_opt(); if (opt) { dpm_trace(DPM_TRACE_SET_OPT_ASYNC, opt); return dpm_md_set_opt(opt, DPM_UNLOCK); } else { dpm_trace(DPM_TRACE_SET_OPT_ASYNC, NULL); dpm_unlock(); return 0; }}/***************************************************************************** * Set operating point synchronously. The caller must clear dpm_sem after the * call returns. *****************************************************************************/intdpm_set_opt_sync(void){ struct dpm_opt *opt = dpm_next_opt(); if (opt) { dpm_trace(DPM_TRACE_SET_OPT_SYNC, opt); return dpm_md_set_opt(opt, DPM_SYNC); } else dpm_trace(DPM_TRACE_SET_OPT_SYNC, NULL); return 0;}/***************************************************************************** * Resynchronize the operating state and the operating point without * blocking. If we don't get the lock it doesn't matter, since whenever the * lock holder releases the lock the resynchronization will be tried again. *****************************************************************************/static inline voiddpm_resync(void){ dpm_trace(DPM_TRACE_RESYNC); if (!dpm_trylock()) dpm_set_opt_async();}voiddpm_resync_task(unsigned long ignore){ dpm_resync();}/***************************************************************************** * unlock the DPM * * If the operating point and operating state are not in sync when dpm_sem is * released, a tasklet is launched to resynchronize them. A tasklet is used * rather than simply calling dpm_set_op directly to avoid deep recursions. * (I'm not sure this has worked, though). * * (The locking functions are inline in dpm_policy.h) * * This is not static since it needs to be called from dpm_policy.c *****************************************************************************/DECLARE_TASKLET(dpm_resync_tasklet, dpm_resync_task, 0);voiddpm_unlock(void){ int retry; retry = dpm_next_opt() != NULL; dpm_trace(DPM_TRACE_UNLOCK, retry); up(&dpm_sem); if (retry) tasklet_schedule(&dpm_resync_tasklet);}/***************************************************************************** * Enter a new operating state for statistical purposes. Returns 1 if the new * state may require a change in operating point and 0 otherwise. * * The normal case that occurs during task scheduling, where we go from task * state to task state, is quickly ignored, as are changes to the * DPM_NO_STATE and changes when DPM is not running. Otherwise, * dpm_enter_state() has advertised that we are in a new state, and indicates * whether an operating point change is required. * * Note the system invariant that the operating point always eventually * catches up with changes to the operating state. This is what makes it * correct here to check for common classes and operating points. We know * that if a common operating point is not the current operating point, it * will be soon. * * The 'quick' variant (in dpm.h) is called out separately to reduce latency * for critical operating state changes where the following are known: 1) The * dpm_policy_lock is held and/or interrupts are properly disabled. 2) DPM is * enabled. 3) The new state is neither DPM_NO_STATE nor the same as the * active state. 4) Any operating point change is being handled elsewhere. *****************************************************************************/static intdpm_enter_state(int new_state){ int ret = 0; unsigned long flags; spin_lock_irqsave(&dpm_policy_lock, flags); if ((new_state == dpm_active_state) || (new_state == DPM_NO_STATE) || !dpm_enabled) { spin_unlock_irqrestore(&dpm_policy_lock, flags); return ret; } if ((dpm_active_policy->classes[new_state] != dpm_active_policy->classes[dpm_active_state]) && (dpm_active_policy->classes[new_state]->opt != dpm_active_policy->classes[dpm_active_state]->opt)) ret = 1; dpm_quick_enter_state(new_state); spin_unlock_irqrestore(&dpm_policy_lock, flags); return ret;}/***************************************************************************** * set operating state * * This is used by the kernel to inform the DPM that the operating state has * changed and that a new operating point should (possibly) be set as a * result. * * If an operating point change is required it is attempted. If we can't get * the lock here, then the operating point change will be activated when the * current lock holder releases the lock. *****************************************************************************/voiddpm_set_os(dpm_state_t new_state){ dpm_trace(DPM_TRACE_SET_OS, new_state); TRACE_DPM(TRACE_EV_DPM_OS, new_state, NULL); if (dpm_enter_state(new_state)) dpm_resync();}EXPORT_SYMBOL(dpm_set_os);/***************************************************************************** * initialize the DPM *****************************************************************************/intdpm_init(void){ trace("in dpm_init\n"); if (dpm_initialized) { trace("DPM already initialized"); return -EALREADY; } /* mutex-style semaphore for access to policies, classes and opts */ init_MUTEX(&dpm_sem); dpm_active_policy = 0; /* this leaves the DPM temporarily disabled until a policy is activated */ dpm_enabled = 0; dpm_initialized = 1; dpm_active_state = DPM_TASK_STATE; trace("DPM is now initialized\n"); return 0;}/***************************************************************************** * (temporarily) disable the DPM *****************************************************************************/intdpm_disable(void){ trace("in dpm_disable\n"); dpm_lock(); dpm_enabled = 0; dpm_active_opt = NULL; dpm_unlock(); trace("DPM is now disabled\n"); return 0;}/***************************************************************************** * re-enable the DPM * dpm_enabled = 1 implies that DPM is initialized and there is an active * policy. The 'enable' call is really designed to be used after a temporary * 'disable'. All that's required to start DPM is to initialize it and set a * policy. *****************************************************************************//* Need to think through enable/disable */intdpm_enable(void){ trace("in dpm_enable\n"); dpm_lock(); if (dpm_active_policy) { dpm_enabled = 1; mb(); dpm_set_opt_sync(); trace("DPM is now enabled\n"); } else { trace("No active policy, dpm_enable is ignored\n"); } dpm_unlock(); return 0;}/***************************************************************************** * Suspend/Resume DPM * The current operating point is saved and restored. This * interface is designed to be used by system suspend/resume code, to safely * save/restore the DPM operating point across a system power-down, where the * firmware may resume the system at a random operating point. This does not * require DPM to be enabled. Note that DPM remains locked across the * suspend/resume. *****************************************************************************/static struct dpm_opt suspend_opt = { name : "[Suspended Op. Point]" };struct dpm_opt *suspended_opt;intdpm_suspend(void){ int err; trace("in dpm_suspend\n"); dpm_lock(); if (dpm_enabled && dpm_active_opt) { suspended_opt = dpm_active_opt; } else { suspended_opt = &suspend_opt; if ((err = dpm_md_get_opt(suspended_opt))) { printk(KERN_CRIT "DPM can not suspend the current op. point!\n"); suspended_opt = NULL; return err; } } return 0;}voiddpm_resume(void){ trace("in dpm_resume\n"); if (suspended_opt) { dpm_active_opt = NULL; /* Force reinitialization of DPM */ dpm_md_set_opt(suspended_opt, DPM_SYNC); suspended_opt = NULL; } dpm_unlock();}/***************************************************************************** * Create a named operating point * The alternate entry point can be used to create anonymous operating points *****************************************************************************/int_dpm_create_opt(struct dpm_opt **p, const char *name, const dpm_md_pp_t * md_pp){ struct dpm_opt *opt; int ret; /* get memory for opt */ if (! (opt = (struct dpm_opt *) kmalloc(sizeof (struct dpm_opt), GFP_KERNEL))) { return -ENOMEM; } trace("%s @ 0x%08lx\n", name, (unsigned long)opt); memset(opt, 0, sizeof(struct dpm_opt)); if (!(opt->name = (char *) kmalloc(strlen(name) + 1, GFP_KERNEL))) { kfree(opt); return -ENOMEM; } /* initialize and validate the opt */ strcpy(opt->name, name); memcpy(&opt->pp, md_pp, DPM_PP_SIZE); ret = dpm_md_init_opt(opt); if (ret) { kfree(opt->name); kfree(opt); return ret; } INIT_LIST_HEAD(&opt->list); *p = opt; return 0;}intdpm_create_opt(const char *name, const dpm_md_pp_t * md_pp){ int ret; struct dpm_opt *opt; trace("in dpm_create_opt for \"%s\"\n", name); dpm_lock(); /* ensure name is unique */ list_find(opt, name, dpm_opts, struct dpm_opt); if (opt) { dpm_unlock(); return -EEXIST; } /* create the opt */ ret = _dpm_create_opt(&opt, name, md_pp); /* add opt to our list */ if (!ret) list_add(&opt->list, &dpm_opts); dpm_unlock(); return ret;}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -