s3c2410电源管理

2019-07-14 00:17发布

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);
}
}
通过如上的方式即管理电源。