一. DPM 概念 Dynamic Power Management
二. Basic Concept of DPM
<include/linux/dpm.h>
操作点:
/* internal representation of an operating point */
struct dpm_opt {
char *name; /* name */
struct list_head list; /* all installed op points */
dpm_md_pp_t pp[DPM_PP_NBR]; /* initialization params */
struct dpm_md_opt md_opt; /* machine dependent part */
int constrained; /* is this opt constrained? */
struct dpm_stats stats; /* statistics */
};
/* Instances of this structure define valid Innovator operating points for DPM.
Voltages are represented in mV, and frequencies are represented in KHz. */
struct dpm_md_opt {
unsigned int v; /* Target voltage in mV */
unsigned int dpll; /* in KHz */
unsigned int cpu; /* CPU frequency in KHz */
unsigned int tc; /* in KHz */
unsigned int per; /* in KHz */
unsigned int dsp; /* in KHz */
unsigned int dspmmu; /* in KHz */
unsigned int lcd; /* in KHz */
struct dpm_regs regs; /* Register values */
};
dpm操做点结构,里面包含了大约16个参数dpm_md_pp_t pp[DPM_PP_NBR];
dpm_md_pp_t是个int型
类和策略:
* internal representation of a class of op points (to be mapped to an
* operating state */
struct dpm_class {
char *name; /* name */
struct list_head list; /* all installed classes */
unsigned nops; /* nbr ops in this class */
struct dpm_opt **ops; /* the ops in this class */
struct dpm_opt *opt; /* the selected op point */
struct dpm_stats stats; /* statistics */
};
/* internal representation of an installed power policy */
struct dpm_policy {
char *name;   /* name */
struct list_head list; /* all installed policies */
struct dpm_class *classes[DPM_STATES]; /* the classes */
struct dpm_stats stats; /* statistics */
};
可以看到操作点,类和策略在系统中以链的形式维护
/* curently installed policies, classes and operating points */
extern struct list_head dpm_policies;
extern struct list_head dpm_classes;
extern struct list_head dpm_opts;
三. Interfaces of DPM
3.1 User Space Interface
<include/linux/dpm.h>
/* single system call to invoke all functions. the "params" argument can be
* NULL for DPM_INIT, DPM_TERMINATE, DPM_DISABLE, and DPM_ENABLE */
#if defined(__KERNEL__)
extern int sys_dpm(int func, struct dpm_param *params);
#else
#define sys_dpm(FUNC, PARAMS) \
syscall(__NR_sys_dpm, FUNC, PARAMS)
#endif
/* function names for sys_dpm system call interface */
typedef enum {
DPM_INIT, /* initialize the DPM */
DPM_TERMINATE, /* terminate the DPM */
DPM_DISABLE, /* temporarily disable the DPM */
DPM_ENABLE, /* re-enable the disabled DPM */
DPM_CREATE_OPT, /* create an operating point */
DPM_CREATE_CLASS, /* create a class of operating points */
DPM_CREATE_POLICY, /* create a policy */
DPM_DESTROY_POLICY, /* destroy a policy */
DPM_SET_POLICY, /* set the active policy */
DPM_GET_POLICY, /* get the name of the active policy */
DPM_GET_ALL_POLICIES, /* get the names of all active policies */
DPM_GET_CLASSES, /* get the names of all active policy */
DPM_SET_TASK_STATE, /* set a task-specific operating state */
DPM_GET_TASK_STATE, /* get a task's task-specific oper state */
/* get statistics */
DPM_GET_POLICY_STATS,
DPM_GET_CLASS_STATS,
DPM_GET_OPT_STATS,
DPM_GET_OS_STATS,
/* debug */
DPM_DISPLAY_POLICY,
DPM_SET_STATE, /* set a (non-scheduling-based) op state */
} dpm_func_t;
asmlinkage int sys_dpm(int func, struct dpm_param *params);
3.2 Interface for Drivers
然后看看dpm为内核和用户态应用程序提供的接口:
<include/linux/dpm.h>
/*
* kernel's operating state interface
*/
/* set operating state */
EXPORT_SYMBOL(dpm_set_os);
void dpm_set_os(dpm_state_t state);
EXPORT_SYMBOL(dpm_init);
EXPORT_SYMBOL(dpm_terminate);
EXPORT_SYMBOL(dpm_disable);
EXPORT_SYMBOL(dpm_enable);
EXPORT_SYMBOL(dpm_create_opt);
EXPORT_SYMBOL(dpm_create_class);
EXPORT_SYMBOL(dpm_create_policy);
EXPORT_SYMBOL(dpm_destroy_policy);
EXPORT_SYMBOL(dpm_set_policy);
/* ?? Needed in kernel mode ?? EXPORT_SYMBOL(dpm_get_policy);*/
EXPORT_SYMBOL(dpm_set_task_state);
EXPORT_SYMBOL(dpm_get_task_state);
/*****************************************************************************
* 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.
*****************************************************************************/
int dpm_suspend(void)
int dpm_resume(void)
3.3 proc fs interface
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* /proc/driver/dpm/cmd (Write-Only)
*
* Writing a string to this file is equivalent to issuing a DPM command.
* Currently only one command per "write" is allowed, and there is a maximum on
* the number of tokens that will be accepted (PAGE_SIZE / sizeof(char *)).
* DPM can be initialized by a linewise copy of a configuration file to this
* /proc file.
*
* DPM Control
* -----------
*
* init : dpm_init()
* enable : dpm_enable()
* disable : dpm_disable()
* terminate : dpm_terminate()
*
* Policy Control
* --------------
*
* set_policy <policy> : Set the policy by name
* set_task_state <pid> <state> : Set the task state for a given pid, 0 = self
*
* Policy Creation
* ---------------
*
* create_opt <name> <pp0> ... <ppn>
* Create a named operating point from DPM_PP_NBR paramaters. All
* parameters must be given. Parameter order and meaning are machine
* dependent.
*
* create_class <name> <opt0> [ ... <optn> ]
* Create a named class from 1 or more named operating points. All
* operating points must be defined before the call.
*
* create_policy <name> <class0> ... <classn>
* Create a named policy from DPM_STATES class names. All classes must be
* defined before the call. The order is machine dependent.
*
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
四. DPM states
那么dpm的状态如何定义的呢?
machine dependent operating state
An operating state is a cpu execution state that (has implications for)(有关,牵涉) power
management. The DPM will select operating points (操作点)based largely on the
current operating state.
*
* DPM_STATES is the number of supported operating states. Valid operating
* states are from 0 to DPM_STATES-1 but when setting an operating state the
* kernel should only specify a state from the set of "base states" and should
* do so by name. During the context switch the new operating state is simply
* extracted from current->dpm_state.
*
* task states:
*
* APIs that reference task states use the range -(DPM_TASK_STATE_LIMIT + 1)
* through +DPM_TASK_STATE_LIMIT. This value is added to DPM_TASK_STATE to
* obtain the downward or upward adjusted task state value. The
* -(DPM_TASK_STATE_LIMIT + 1) value is interpreted specially, and equates to
* DPM_NO_STATE.
*
* Tasks inherit their task operating states across calls to
* fork(). DPM_TASK_STATE is the default operating state for all tasks, and is
* inherited from init. Tasks can change (or have changed) their tasks states
* using the DPM_SET_TASK_STATE variant of the sys_dpm() system call. */
<include/asm-arm/arch-omap24xx/omap2420_dpm.h>
#define DPM_NO_STATE -1 , 这个状态是不受电源管理的状态,有些任务根本不受dpm的托管。
以下三个是dpm的基本状态
/*states below are dpm base states, dm基本状态*/
#define DPM_RELOCK_STATE 0 //??
#define DPM_IDLE_TASK_STATE 1
#define DPM_IDLE_STATE 2
#define DPM_SLEEP_STATE 3
#define DPM_BASE_STATES 4 , 定义了dpm的基本状态的数量,只有四种。
以下几个定义了dpm task state的状态,这些state和task相关。可以通过sys_dpm(DPM_SET_TASK_STATE)来设定。
#define DPM_TASK_STATE_LIMIT 4 ,定义了dpm的task operating state的最大数量
/*DPM_TASK_STATE is the 缺省状态 for all tasks, and is
* inherited from init. */
#define DPM_TASK_STATE (DPM_BASE_STATES + DPM_TASK_STATE_LIMIT)
/*number of supported operating states*/
#define DPM_STATES (DPM_TASK_STATE + DPM_TASK_STATE_LIMIT + 1) ,定义了整个dpm operating state的数量。
#define DPM_TASK_STATES (DPM_STATES - DPM_BASE_STATES)
#define DPM_STATE_NAMES \
{ "relock", "idle-task", "idle", "sleep",\
"task-4", "task-3", "task-2", "task-1",\
"task", \
"task+1", "task+2", "task+3", "task+4" \
}
sys_dpm() -> u_dpm_set_task_state() -> dpm_set_task_state() -> dpm_set_os()
在看一个设定task state的操作:
static int u_dpm_set_task_state(pid_t pid, dpm_state_t task_state)
{
return dpm_set_task_state(pid, task_state);
}
/*****************************************************************************
* set a task state
*****************************************************************************/
int
dpm_set_task_state(pid_t pid, dpm_state_t task_state)
{
struct task_struct *p;
/*task_state值的合法性检查*/
if (task_state == -(DPM_TASK_STATE_LIMIT + 1))
task_state = DPM_NO_STATE;
else if (abs(task_state) > DPM_TASK_STATE_LIMIT)
{
dpm_trace(DPM_TRACE_SET_TASK_STATE, pid, task_state, -EINVAL);
return -EINVAL;
}
else
{
/*task_state值得调整,DPM_TASK_STATE是一个默认值
设定的值比默认值或大或小,这样相当于加了
个基准值而已-(DPM_TASK_STATE_LIMIT + 1) < task_state <= DPM_TASK_STATE_LIMIT*/
task_state += DPM_TASK_STATE;
}
read_lock(&tasklist_lock);
if (pid == 0)
p = current;
else
p = find_task_by_pid(pid);
if (!p) {
read_unlock(&tasklist_lock);
dpm_trace(DPM_TRACE_SET_TASK_STATE, pid, task_state, -ENOENT);
return -ENOENT;
}
p->dpm_state = task_state;
read_unlock(&tasklist_lock);
dpm_trace(DPM_TRACE_SET_TASK_STATE, pid, task_state, 0);
/*如果是当前进程,马上切换状态*/
if (pid == 0)
dpm_set_os(p->dpm_state);
return 0;
}
以下的低层调用 dpm_set_os() -> dpm_resync() -> dpm_set_opt_async() -> dpm_md_set_opt() -> dpm_omap24xx_set_opt()
dpm_omap24xx_set_opt() 是一个与体系结构相关的set opt函数,在初始化的时候被注册。下面还会分析。
以下更低层的的调用最终会调用到omap24xx_fscaler(),定义在<arch/arm/mach-omap24xx/omap24xx_dpm.c>中,
这个函数做实际的事情,根据设定好的操作点设定cpu和相关硬件的状态,完成了dpm状态的切换。比如:
//= POWER MODES =//
#define PRCM_OFF 0
#define PRCM_ON 1
#define PRCM_RETENTION 2
#define PRCM_SLEEP 3
#define PRCM_STANDBY 4
#define PRCM_DORMANT 5
static void omap24xx_fscaler(struct dpm_regs *regs) -> omap24xx_pm_suspend(PRCM_STANDBY) ...
Just a piece of code:
if(dpm_fscaler_flags & DPM_FSCALER_ANY_SLEEPMODE) {
/* omap24xx_pm_suspend() handles voltage scaling */
DPRINT("\nsuspending devices\n\n");
device_suspend(0, SUSPEND_POWER_DOWN);
if(dpm_fscaler_flags & DPM_FSCALER_SLEEP_DEVICEON) {
omap24xx_pm_suspend(PRCM_ON);
}
else if(dpm_fscaler_flags & DPM_FSCALER_SLEEP_STANDBY){
omap24xx_pm_suspend(PRCM_STANDBY);
}
else if(dpm_fscaler_flags & DPM_FSCALER_SLEEP_SLEEP){
omap24xx_pm_suspend(PRCM_SLEEP);
}
else if(dpm_fscaler_flags & DPM_FSCALER_SLEEP_RETENTION){
omap24xx_pm_suspend(PRCM_RETENTION);
}
/* Here when we wake up. */
/* Power on devices again */
device_resume(RESUME_POWER_ON);
五. Kernel supported on DPM
5.1 task_struct
在task_struct中,添加了对dpm的支持:
/* dynamic power management */
int dpm_state;
这个状态就设定为上述提到的dpm task struct task状态之一。用dpm_set_task_state()来进行状态的切换。
子进程继承父进程的dpm状态,
另外,在支持dpm的kernel中,有许多类似的结构都添加了对dpm的支持。例如:
struct device_driver {
char * name;
struct bus_type * bus;
struct device_class * devclass;
struct semaphore unload_sem;
struct kobject kobj;
struct list_head bus_list;
struct list_head class_list;
struct list_head devices;
int (*probe) (struct device * dev);
int (*remove) (struct device * dev);
void (*shutdown) (struct device * dev);
int (*suspend) (struct device * dev, u32 state, u32 level);
int (*resume) (struct device * dev, u32 level);
#if 1 /* linux-pm */
int (*scale) (struct bus_op_point * op, u32 level);
#endif /* linux-pm */
};
整个dpm架构在Linux kernel中以device driver的形式存在,提供了支持应用程序和内核及其他driver的接口。
六. DPM Initializing
了解了这些知识,看看dpm的初始化:
<drivers/dpm.c>
一看这个目录就知道了这个可能是个driver,至少是个内核模块,至于怎么编译就不管了,可以静态编译到内核中.所以你肯定会去找module_init拉.
#ifdef MODULE
module_init(dpm_init_module);
module_exit(dpm_exit_module);
#else
__initcall(dpm_init_module);
#endif
看见了初始化函数是dpm_init_module:
int dpm_init_module(void)
{
#ifndef CONFIG_MACH_OMAP1610_STBPM
/* ensure syscall slot is available */
if (sys_call_table[sys_dpm_nr] != sys_call_table[0])
return 1;
/* install our syscall */
sys_call_table[sys_dpm_nr] = sys_dpm;
#ifdef CONFIG_PROC_FS
{
void dpm_proc_init(void);
dpm_proc_init();
}
#endif
dpm_md_init();
trace("DPM is nw installed\n");
#endif /* CONFIG_MACH_OMAP1610_STBPM */
return 0;
}
(1) 注册自己的系统调用 sys_dpm,作为用户态应用程序的接口
(2) 调用dpm_md_init()完成自己的初始化.
static inline void dpm_md_init(void)
{
if (dpm_md.init)
dpm_md.init();
}
<arch/arm/mach-omap24xx/omap24xx_dpm.c>
int __init dpm_omap24xx_init(void)
{
printk("OMAP24XX Dynamic Power Management\n");
/*在这个地方连接cpu相关的初始化函数*/
dpm_md.init = NULL;
dpm_md.init_opt = dpm_omap24xx_init_opt;
dpm_md.set_opt = dpm_omap24xx_set_opt;
dpm_md.get_opt = dpm_omap24xx_get_opt;
dpm_md.validate_opt = dpm_omap24xx_validate_opt;
dpm_md.idle_set_parms = NULL;
dpm_md.cleanup = dpm_omap24xx_cleanup;
#ifdef MAYBE
/* +++ necessary? */
dpm_omap24xx_board_setup();
#endif
#ifdef LATER
dpm_bd.init();
#endif
omap24xx_clk_init();
omap24xx_vcs_init();
return 0;
}
七. DPM idle and DPM suspend
说一下idle:
<arch/arm/mach-omap24xx/omap2_dpm.c>
int __init
dpm_omap_init(void)
{
printk("TI OMAP Dynamic Power Management.\n");
dpm_md.init_opt = dpm_omap_init_opt;
dpm_md.set_opt = dpm_omap_set_opt;
dpm_md.get_opt = dpm_omap_get_opt;
dpm_md.validate_opt = dpm_omap_validate_opt;
dpm_md.idle_set_parms = NULL;
dpm_md.cleanup = dpm_omap_cleanup;
return 0;
}
__initcall(dpm_omap_init);
发现了对硬件的实际操作基本都在<arch/arm/mach-omap24xx/pm.c>中定义。
static int __init omap_pm_init(void)
{
unsigned char addr, data;
u32 yy;
u32 zz;
printk("Power Management for TI OMAP.\n");
pm_idle = dpm_idle;
memcpy((void *)ISRAM_PROGRAM, omap2420_cpu_suspend,
omap2420_cpu_suspend_sz);
addr = 0x55; /* IOSTATE2 */
Spi1_read(SPI1_CS1, 1, &addr, &data);
if((data & 0x04) == 0x04){ /* BRB? */
pm_BRB_mode = 1;
}
else {
pm_BRB_mode = 0;
}
/* GPIO wakeup settings */
__raw_writel(0x0002A000, OMAP2420_GPIO1_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO1_SETWKUENA */
__raw_writel(0x0000000C, OMAP2420_GPIO2_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO2_SETWKUENA */
__raw_writel(0x00004010, OMAP2420_GPIO3_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO3_SETWKUENA */
__raw_writel(0x00100020, OMAP2420_GPIO4_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO4_SETWKUENA */
__raw_writel(0x00000000, OMAP2420_GPIO5_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO5_SETWKUENA */
#ifdef CONFIG_ICE_PATCH_PM
if(pm_BRB_mode)
{
request_irq(INT_GPIO_121,gpio121_dummy_handler,SA_INTERRUPT,"gpio121_dummy",NULL);
__REG32(0x49012034) |= (1<<25) ;
__REG32(0x49012040) &= ~(1<<25);
__REG32(0x49012044) |= (1<<25) ;
__REG32(0x4901201C) |= (1<<25) ;
__REG32(0x49012048) |= (1<<25) ;
__raw_writel(0x02100020, OMAP2420_GPIO4_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO4_SETWKUENA */
}
#endif
if(stm_dev_sts[DRV_UART1] == STAT_OFF) {
/* disable UART1 function clock */
CM_FCLKEN1_CORE &= ~(1<<21); /* CM_FCLKEN1_CORE:EN_UART1 */
CM_ICLKEN1_CORE &= ~(1<<21); /* CM_ICLKEN1_CORE:EN_UART1 */
}
yy = __REG32(0x6D000060) & 0x0000FF00; /* SDRC_DLLA_CTRL(0x6D000060) 0x0000yy00 */
zz = __REG32(0x6D000068) & 0x0000FF00; /* SDRC_DLLB_CTRL(0x6D000068) 0x0000zz00 */
*((u32 *)(ISRAM_PROGRAM + omap2420_val_SDRC_DLLA_CTRL_yy_offset)) = yy;
*((u32 *)(ISRAM_PROGRAM + omap2420_val_SDRC_DLLB_CTRL_zz_offset)) = zz;
pm_loops_per_jiffy = loops_per_jiffy;
return 0;
}
__initcall(omap_pm_init);
可以看到很多都是对硬件设备的直接操作。
那么既然这里把pm_idle初始化成了dpm_idle(),所以dpm_idle就是实际的dpm idle state的处理函数。
我们看在linux中何时会处理dpm idle state,也就是说硬件驱动层和内核的接口。
<arch/arm/kernel/process.c>,定义了这个接口。
void (*pm_power_off)(void);
void (*pm_idle)(void);
EXPORT_SYMBOL(pm_idle); pm_idle也会被kernel export出来,做为驱动程序的接口
/*
* The idle thread. We try to conserve power, while trying to keep
* overall latency low. The architecture specific idle is passed
* a value to indicate the level of "idleness" of the system.
*/
void cpu_idle(void)
{
/* endless idle loop with no priority at all */
while (1) {
while (!need_resched()) {
void (*idle)(void) = pm_idle;
if (!idle)
idle = default_idle;
leds_event(led_idle_start);
dpm_set_os(DPM_IDLE_STATE);
idle();
dpm_set_os(DPM_IDLE_TASK_STATE);
leds_event(led_idle_end);
}
schedule();
#ifndef CONFIG_NO_PGT_CACHE
check_pgt_cache();
#endif
}
}
最关键的几句:
dpm_set_os(DPM_IDLE_STATE);
idle(); (由上述分析,实际会调用dpm_idle())
dpm_set_os(DPM_IDLE_TASK_STATE);
看看dpm_idle里面到底做了什么:
/* If DPM is currently disabled here we simply do the standard
idle wait.
If we're not actually in DPM_IDLE_TASK_STATE, we need to go back and get
into this state. This could happen in rare instances - an interrupt between
dpm_set_os() and the critical section.
If we are not yet at the idle-task operating point, or if there is no
difference between idle-task and idle, we can enter/exit the idle state
quickly since it's only for statistical purposes. This is also true if for
some reason we can't get the DPM lock, since obviously an asynchronous event
is going to have to occur to clear the lock, and this event is going to take
us out of idle.
Otherwise the full idle shutdown is done. */
dpm_idle() -> full_idle()-> omap24xx_pm_idle()
这个是NEC dpm中做实际事情的最内层的调用函数:
void omap24xx_pm_idle(void)
{
TRACE_PROCESS(TRACE_EV_PROCESS_IDLE_BEGIN, 0, 0);
if(dpm_enabled) {
if(status_idle_judge_func() == STM_IDLE) {
device_suspend(0, SUSPEND_POWER_DOWN);
local_irq_disable();
local_fiq_disable();
if( OMAP_SLEEP_ENABLE == do_sleep_setup() ) {
int dummy = PRCM_SLEEP;
omap24xx_pm_suspend(dummy);
}
local