linux中,电源管理,分为apm,acpi两种电源管理方式,两者不建议同时使用。而对于S3c2410来说,
电源管理是采用apm.
我们就先看下apm:
apm提供了一种用户可控制的通信方式,注册设备:
apm注册了一个apm_bios设备,通过对这个设备的操作即达到获取apm的相关事件和操作。
下面就来看下代码是如何实现的:
static int __init apm_init(void)
{
int ret;
if (apm_disabled) {
printk(KERN_NOTICE "apm: disabled on user request./n");
return -ENODEV;
}
if (PM_IS_ACTIVE()) {
printk(KERN_NOTICE "apm: overridden by ACPI./n");
return -EINVAL;
}
pm_active = 1;
ret = kernel_thread(kapmd, NULL, CLONE_KERNEL);
if (ret < 0) {
pm_active = 0;
return ret;
}
#ifdef CONFIG_PROC_FS
create_proc_info_entry("apm", 0, NULL, apm_get_info);
#endif
ret = misc_register(&apm_device);
//这个地方就注册了apm_bios设备.
if (ret != 0) {
remove_proc_entry("apm", NULL);
pm_active = 0;
wake_up(&kapmd_wait);
wait_for_completion(&kapmd_exit);
}
return ret;
}
static struct file_operations apm_bios_fops = {
.owner = THIS_MODULE,
.read = apm_read,
.poll = apm_poll,
.ioctl = apm_ioctl,
.open = apm_open,
.release = apm_release,
};
static struct miscdevice apm_device = {
.minor = APM_MINOR_DEV,
.name = "apm_bios",
.fops = &apm_bios_fops
};
这样就我们就可以像对一般的设备文件一样,读取apm_bios的相关信息了。
首先是open
static int apm_open(struct inode * inode, struct file * filp)
{
struct apm_user *as;
as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL);
if (as) {
memset(as, 0, sizeof(*as));
/*
* XXX - this is a tiny bit broken, when we consider BSD
* process accounting. If the device is opened by root, we
* instantly flag that we used superuser privs. Who knows,
* we might close the device immediately without doing a
* privileged operation -- cevans
*/
as->suser = capable(CAP_SYS_ADMIN);
as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
down_write(&user_list_lock);
list_add(&as->list, &apm_user_list);
up_write(&user_list_lock);
filp->private_data = as;
}
return as ? 0 : -ENOMEM;
}
struct apm_user {
struct list_head list;
unsigned int suser: 1;
unsigned int writer: 1;
unsigned int reader: 1;
int suspend_result;
unsigned int suspend_state;
#define SUSPEND_NONE 0 /* no suspend pending */
#define SUSPEND_PENDING 1 /* suspend pending read */
#define SUSPEND_READ 2 /* suspend read, pending ack */
#define SUSPEND_ACKED 3 /* suspend acked */
#define SUSPEND_DONE 4 /* suspend completed */
struct apm_queue queue;
};
/
这个关键是apm_user结构变量as,它是用户和apm内核部分沟通的桥梁,当有apm事件发生时,
就把event挂到apm_user的queue上,这样当用户读时就会读到相关事件然后处理。
static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
{
struct apm_user *as = fp->private_data;
apm_event_t event;
int i = count, ret = 0;
if (count < sizeof(apm_event_t))
return -EINVAL;
if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
return -EAGAIN;
wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
//这个就等待queue非空,即等待apm事件的来临:
while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
event = queue_get_event(&as->queue);
ret = -EFAULT;
if (copy_to_user(buf, &event, sizeof(event)))
break;
if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)
as->suspend_state = SUSPEND_READ;
buf += sizeof(event);
i -= sizeof(event);
}
if (i < count)
ret = count - i;
return ret;
}
再来看ioctl函数:
static int
apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
{
struct apm_user *as = filp->private_data;
unsigned long flags;
int err = -EINVAL;
if (!as->suser || !as->writer)
return -EPERM;
//只有超级用户才能执行回复。
switch (cmd) {
case APM_IOC_SUSPEND:
as->suspend_result = -EINTR;
if (as->suspend_state == SUSPEND_READ) {
//这个就是当user读取到event时的状态,这是发送这个事件意味这是回应ack。
/*
* If we read a suspend command from /dev/apm_bios,
* then the corresponding APM_IOC_SUSPEND ioctl is
* interpreted as an acknowledge.
*/
as->suspend_state = SUSPEND_ACKED;
suspends_pending--;
} else {
/*
* Otherwise it is a request to suspend the system.
* Queue an event for all readers, and expect an
* acknowledge from all writers who haven't already
* acknowledged.
*/
queue_event(APM_USER_SUSPEND, as);
}
/*
* If there are no further acknowledges required, suspend
* the system.
*/
if (suspends_pending == 0)
apm_suspend();
/*
* Wait for the suspend/resume to complete. If there are
* pending acknowledges, we wait here for them.
*
* Note that we need to ensure that the PM subsystem does
* not kick us out of the wait when it suspends the threads.
*/
flags = current->flags;
current->flags |= PF_NOFREEZE;
/*
* Note: do not allow a thread which is acking the suspend
* to escape until the resume is complete.
*/
if (as->suspend_state == SUSPEND_ACKED)
wait_event(apm_suspend_waitqueue,
as->suspend_state == SUSPEND_DONE);
else
wait_event_interruptible(apm_suspend_waitqueue,
as->suspend_state == SUSPEND_DONE);
//等待所有用户回应了事件,这个在apm_suspend函数中将所有apm_user赋值为suspend_state == SUSPEND_DONE。
current->flags = flags;
err = as->suspend_result;
as->suspend_state = SUSPEND_NONE;
break;
}
return err;
}
static void queue_event(apm_event_t event, struct apm_user *sender)
{
struct apm_user *as;
down_read(&user_list_lock);
list_for_each_entry(as, &apm_user_list, list) {
if (as != sender && as->reader)
queue_event_one_user(as, event);
//这个是将这个事件发给每个需要知道事件的apm_user
}
up_read(&user_list_lock);
wake_up_interruptible(&apm_waitqueue);
}
static void queue_event_one_user(struct apm_user *as, apm_event_t event)
{
if (as->suser && as->writer) {
switch (event) {
case APM_SYS_SUSPEND:
case APM_USER_SUSPEND:
/*
* If this user already has a suspend pending,
* don't queue another one.
*/
if (as->suspend_state != SUSPEND_NONE)
return;
as->suspend_state = SUSPEND_PENDING;
suspends_pending++;
break;
}
}
queue_add_event(&as->queue, event);
}
上面要注意的一点是 suspends_pending,这个是全局变量,且只有suer即超级用户才执行suspends_pending++,
也就是只有超级用户,内核才等待其回复。
所有用户回复了,可以执行了suspend了。
static void apm_suspend(void)
{
struct apm_user *as;
int err = pm_suspend(PM_SUSPEND_MEM);
//为啥只是 PM_SUSPEND_MEM,这个就是本身就是suspend的一种,在s3c2410里,只支持这一种。
这个在下面的s3c2410_pm_enter可以看到。
/*
* Anyone on the APM queues will think we're still suspended.
* Send a message so everyone knows we're now awake again.
*/
queue_event(APM_NORMAL_RESUME, NULL);
/*
* Finally, wake up anyone who is sleeping on the suspend.
*/
down_read(&user_list_lock);
list_for_each_entry(as, &apm_user_list, list) {
as->suspend_result = err;
as->suspend_state = SUSPEND_DONE;
}
up_read(&user_list_lock);
wake_up(&apm_suspend_waitqueue);
}
int pm_suspend(suspend_state_t state)
{
if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)
return
enter_state(state);
return -EINVAL;
}
static int enter_state(suspend_state_t state)
{
int error;
if (!valid_state(state))
return -ENODEV;
if (!mutex_trylock(&pm_mutex))
return -EBUSY;
if (state == PM_SUSPEND_DISK) {
error = pm_suspend_disk();
goto Unlock;
}
pr_debug("PM: Preparing system for %s sleep/n", pm_states[state]);
if ((error = suspend_prepare(state)
))
goto Unlock;
pr_debug("PM: Entering %s sleep/n", pm_states[state]);
error = suspend_enter(state);
pr_debug("PM: Finishing wakeup./n");
suspend_finish(state);
Unlock:
mutex_unlock(&pm_mutex);
return error;
}
int suspend_enter(suspend_state_t state)
{
int error = 0;
unsigned long flags;
local_irq_save(flags);
if ((error = device_power_down(PMSG_SUSPEND))) {
printk(KERN_ERR "Some devices failed to power down/n");
goto Done;
}
error = pm_ops->enter(state);
device_power_up();
Done:
local_irq_restore(flags);
return error;
}
而这个pm_ops就是用set_pm_ops赋值的。
void pm_set_ops(struct pm_ops * ops)
{
mutex_lock(&pm_mutex);
pm_ops = ops;
mutex_unlock(&pm_mutex);
}
可想而知s3c2410的电源管理要想起作用,也必须通过这个函数注册。
然后看下s3c2410的电源管理如何和这个关联的。
int __init s3c2410_pm_init(void)
{
printk("S3C2410 Power Management, (c) 2004 Simtec Electronics/n");
pm_set_ops(&s3c2410_pm_ops);
return 0;
}
这样执行上面的pm_enter时就会执行 s3c2410_pm_enter。
static int s3c2410_pm_enter(suspend_state_t state)
{
unsigned long regs_save[16];
unsigned long tmp;
/* ensure the debug is initialised (if enabled) */
s3c2410_pm_debug_init();
DBG("s3c2410_pm_enter(%d)/n", state);
if (state != PM_SUSPEND_MEM) {
printk(KERN_ERR PFX "error: only PM_SUSPEND_MEM supported/n");
return -EINVAL;
}
/* check if we have anything to wake-up with... bad things seem
* to happen if you suspend with no wakeup (system will often
* require a full power-cycle)
*/
if (!any_allowed(s3c_irqwake_intmask, s3c_irqwake_intallow) &&
!any_allowed(s3c_irqwake_eintmask, s3c_irqwake_eintallow)) {
printk(KERN_ERR PFX "No sources enabled for wake-up!/n");
printk(KERN_ERR PFX "Aborting sleep/n");
return -EINVAL;
}
/* prepare check area if configured */
s3c2410_pm_check_prepare();
/* store the physical address of the register recovery block */
s3c2410_sleep_save_phys = virt_to_phys(regs_save);
DBG("s3c2410_sleep_save_phys=0x%08lx/n", s3c2410_sleep_save_phys);
/* ensure at least GESTATUS3 has the resume address */
__raw_writel(virt_to_phys(s3c2410_cpu_resume), S3C2410_GSTATUS3);
DBG("GSTATUS3 0x%08x/n", __raw_readl(S3C2410_GSTATUS3));
DBG("GSTATUS4 0x%08x/n", __raw_readl(S3C2410_GSTATUS4));
/* save all necessary core registers not covered by the drivers */
s3c2410_pm_do_save(gpio_save, ARRAY_SIZE(gpio_save));
s3c2410_pm_do_save(irq_save, ARRAY_SIZE(irq_save));
s3c2410_pm_do_save(core_save, ARRAY_SIZE(core_save));
s3c2410_pm_do_save(uart_save, ARRAY_SIZE(uart_save));
/* set the irq configuration for wake */
s3c2410_pm_configure_extint();
DBG("sleep: irq wakeup masks: %08lx,%08lx/n",
s3c_irqwake_intmask, s3c_irqwake_eintmask);
__raw_writel(s3c_irqwake_intmask, S3C2410_INTMSK);
__raw_writel(s3c_irqwake_eintmask, S3C2410_EINTMASK);
/* ack any outstanding external interrupts before we go to sleep */
__raw_writel(__raw_readl(S3C2410_EINTPEND), S3C2410_EINTPEND);
/* flush cache back to ram */
arm920_flush_kern_cache_all();
s3c2410_pm_check_store();
// need to make some form of time-delta
/* send the cpu to sleep... */
__raw_writel(0x00, S3C2410_CLKCON); /* turn off clocks over sleep */
s3c2410_cpu_suspend(
regs_save
);
//这个是汇编级的进入sleep状态的代码。
/* unset the return-from-sleep flag, to ensure reset */
tmp = __raw_readl(S3C2410_GSTATUS2);
tmp &= S3C2410_GSTATUS2_OFFRESET;
__raw_writel(tmp, S3C2410_GSTATUS2);
/* restore the system state */
s3c2410_pm_do_restore_core(core_save, ARRAY_SIZE(core_save));
s3c2410_pm_do_restore(gpio_save, ARRAY_SIZE(gpio_save));
s3c2410_pm_do_restore(irq_save, ARRAY_SIZE(irq_save));
s3c2410_pm_do_restore(uart_save, ARRAY_SIZE(uart_save));
s3c2410_pm_debug_init();
/* check what irq (if any) restored the system */
DBG("post sleep: IRQs 0x%08x, 0x%08x/n",
__raw_readl(S3C2410_SRCPND),
__raw_readl(S3C2410_EINTPEND));
s3c2410_pm_show_resume_irqs(IRQ_EINT0, __raw_readl(S3C2410_SRCPND),
s3c_irqwake_intmask);
s3c2410_pm_show_resume_irqs(IRQ_EINT4-4, __raw_readl(S3C2410_EINTPEND),
s3c_irqwake_eintmask);
DBG("post sleep, preparing to return/n");
s3c2410_pm_check_restore();
/* ok, let's return from sleep */
DBG("S3C2410 PM Resume (post-restore)/n");
return 0;
}
ENTRY(s3c2410_cpu_suspend)
stmfd sp!, { r4 - r12, lr }
@@ store co-processor registers
mrc p15, 0, r4, c15, c1, 0 @ CP access register
mrc p15, 0, r5, c13, c0, 0 @ PID
mrc p15, 0, r6, c3, c0, 0 @ Domain ID
mrc p15, 0, r7, c2, c0, 0 @ translation table base address
mrc p15, 0, r8, c2, c0, 0 @ auxiliary control register
mrc p15, 0, r9, c1, c0, 0 @ control register
stmia r0, { r4 - r13 }
//这个就是将r4-r13保存到 regs_save
@@ flush the caches to ensure everything is back out to
@@ SDRAM before the core powers down
bl arm920_flush_kern_cache_all
@@ prepare cpu to sleep
ldr r4, =S3C2410_REFRESH
ldr r5, =S3C2410_MISCCR
ldr r6, =S3C2410_CLKCON
ldr r7, [ r4 ] @ get REFRESH (and ensure in TLB)
ldr r8, [ r5 ] @ get MISCCR (and ensure in TLB)
ldr r9, [ r6 ] @ get CLKCON (and ensure in TLB)
orr r7, r7, #S3C2410_REFRESH_SELF @ SDRAM sleep command
orr r8, r8, #S3C2410_MISCCR_SDSLEEP @ SDRAM power-down signals
orr r9, r9, #S3C2410_CLKCON_POWER @ power down command
teq pc, #0 @ first as a trial-run to load cache
bl s3c2410_do_sleep
teq r0, r0 @ now do it for real
b s3c2410_do_sleep @
@@ align next bit of code to cache line
.align 8
s3c2410_do_sleep:
streq r7, [ r4 ] @ SDRAM sleep command
streq r8, [ r5 ] @ SDRAM power-down config
streq r9, [ r6 ] @ CPU sleep
//进入sleep
1: beq 1b
//因为第一次是pc,#进行比较,所以上面不会执行,因为不eq
mov pc, r14
电源管理机制讲完,但是在应用层如何实现控制呢:
#include
#include
#include
#include
#define APM_IOC_STANDBY _IO('A', 1)
#define APM_IOC_SUSPEND _IO('A', 2)
//#define _IO(x,y) (((x)<<8)|y)
int main(){
int file=open("/dev/misc/apm_bios
",O_WRONLY);
//打开电源管理设备文件。
if(file>0){
printf("open amp_bios/n");
ioctl(file,APM_IOC_SUSPEND);
close(file);
}
}
通过如上的方式即管理电源。