Chinaunix首页 | 论坛 | 博客
  • 博客访问: 879042
  • 博文数量: 149
  • 博客积分: 3671
  • 博客等级: 中校
  • 技术积分: 1701
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-03 16:52
文章分类

全部博文(149)

文章存档

2011年(57)

2010年(92)

分类: LINUX

2010-06-10 10:54:01

Linux APM在ARM上的实现原理

内核:linux2.6.30.10

参考:

http://blog.csdn.net/hongjiujing/archiv ... 22726.aspx
http://blog.163.com/postmessage@126/blo ... 855826639/
http://blog.chinaunix.net/u2/89957/showart_2032533.html



由于arm系统中没有bios设备, 所以只能为arm系统创建一个虚拟的字符设备与用户空间进行通讯. 即在apm中实现一个misc设备,实质上也是一个字符设备, misc设备的主设备号是10, 而apm_bios作为个misc设备, 次设备号是134。 Linux2.6.30.10内核的/drivers/char/apm-emulation.c提供了apm_bios的驱动模型,也就是系统进入睡眠的入口函数,更早的版本的接口文件为:arch/arm/kernel/apm.c
在apm-emulation.c中:
/*
* The apm_bios device is one of the misc char devices.
* This is its minor number.
*/
#define APM_MINOR_DEV 134

这个apm_bios设备通过ioctl系统调用和用户空间进行通讯, 即当用户进程通过ioctl发来suspend命令时, 它就传给内核, 使系统进入suspend状态.

1,初始化

static int __init apm_init(void)
{
int ret;
if (apm_disabled) {
printk(KERN_NOTICE "apm: disabled on user request.\n");
return -ENODEV;
}
//创建一个线程, 用于处理事件队列, 工作函数是kapmd
kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
if (IS_ERR(kapmd_tsk)) {
ret = PTR_ERR(kapmd_tsk);
kapmd_tsk = NULL;
goto out;
}
wake_up_process(kapmd_tsk);
//通过proc,向用户空间输出apm信息
#ifdef CONFIG_PROC_FS
proc_create("apm", 0, NULL, &apm_proc_fops);
#endif
//注册misc设备
ret = misc_register(&apm_device);
if (ret)
goto out_stop;
ret = register_pm_notifier(&apm_notif_block);
if (ret)
goto out_unregister;
return 0;
out_unregister:
misc_deregister(&apm_device);
out_stop:
remove_proc_entry("apm", NULL);
kthread_stop(kapmd_tsk);
out:
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的相关信息了。

2,结构中函数实现

当一个用户进程打开apm_bios设备时, 它就会调用这个函数

static int apm_open(struct inode * inode, struct file * filp)
{
//这个关键是apm_user结构变量as,它是用户和apm内核部分沟通的桥梁,当有apm事件发生时,就把event挂到apm_user的queue上,这样当用户读时就会读到相关事件然后处理。
struct apm_user *as;
lock_kernel();
//分配一个apm_user结构, 来表示一个用户进程
as = kzalloc(sizeof(*as), GFP_KERNEL);
//读写等权限设置
if (as) {
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;
}
unlock_kernel();
return as ? 0 : -ENOMEM;
}
当用户空间进程去读这个设备时, 这个函数就会被调用. 这个函数的主要作用是将事件读出到用户空间.
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));
//将队列中的事件复制给用户空间
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;
//设置状态
mutex_lock(&state_lock);
if (as->suspend_state == SUSPEND_PENDING &&
(event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
as->suspend_state = SUSPEND_READ;
mutex_unlock(&state_lock);
buf += sizeof(event);
i -= sizeof(event);
}
if (i < count)
ret = count - i;
return ret;
}
//这个poll/select的后端实现, 用于查询有没有数据可读
static unsigned int apm_poll(struct file *fp, poll_table * wait)
{
struct apm_user *as = fp->private_data;
poll_wait(fp, &apm_waitqueue, wait);
return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
}
//这个是这个设备的核心函数, 用于内核与用户空间交互
static int apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
{
struct apm_user *as = filp->private_data;
int err = -EINVAL;
//只有超级用户才能执行回复
if (!as->suser || !as->writer)
return -EPERM;
switch (cmd) {
case APM_IOC_SUSPEND:
mutex_lock(&state_lock);
as->suspend_result = -EINTR;
switch (as->suspend_state) {
//这个就是当user读取到event时的状态,这是发送这个事件,意味着这是回应ack
case SUSPEND_READ:
as->suspend_state = SUSPEND_ACKED;
atomic_dec(&suspend_acks_pending);
mutex_unlock(&state_lock);
wake_up(&apm_suspend_waitqueue);
freezer_do_not_count();
wait_event(apm_suspend_waitqueue, as->suspend_state == SUSPEND_DONE);
freezer_count();
break;
case SUSPEND_ACKTO:
as->suspend_result = -ETIMEDOUT;
mutex_unlock(&state_lock);
break;
default:
as->suspend_state = SUSPEND_WAIT;
mutex_unlock(&state_lock);
as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
}
mutex_lock(&state_lock);
err = as->suspend_result;
as->suspend_state = SUSPEND_NONE;
mutex_unlock(&state_lock);
break;
}
return err;
}

3, 事件队列函数

static void queue_event(apm_event_t event)
{
struct apm_user *as;

down_read(&user_list_lock);
list_for_each_entry(as, &apm_user_list, list) {
if (as->reader)
//这个是将这个事件发给每个需要知道事件的apm_user
queue_add_event(&as->queue, event);
}
up_read(&user_list_lock);
//唤醒等待读的进程
wake_up_interruptible(&apm_waitqueue);
}

static void queue_add_event(struct apm_queue *q, apm_event_t event)
{
q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
if (q->event_head == q->event_tail) {
static int notified;
if (notified++ == 0)
printk(KERN_ERR "apm: an event queue overflowed\n");
q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
}
q->events[q->event_head] = event;
}

4,所有用户回复了,可以执行ioctl中的pm_suspend了这部分说明kernel里面的电源管理的核心函数,这部分的代码在/kernel/power/suspend.c中

int pm_suspend(suspend_state_t state)
{
if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)
return enter_state(state);
return -EINVAL;
}
调用enter_state(),同样在supend.c中
int enter_state(suspend_state_t state)
{
int error;
if (!valid_state(state))
return -ENODEV;
//获得锁, 参见注释
if (!mutex_trylock(&pm_mutex))
return -EBUSY;
printk(KERN_INFO "PM: Syncing filesystems ... ");
sys_sync();
printk("done.\n");
//prepare阶段
pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
error = suspend_prepare();
if (error)
goto Unlock;
if (suspend_test(TEST_FREEZER))
goto Finish;
//进入阶段
pr_debug("PM: Entering %s sleep\n", pm_states[state]);
error = suspend_devices_and_enter(state);
//完成挂起, 恢复状态
Finish:
pr_debug("PM: Finishing wakeup.\n");
suspend_finish();
Unlock:
mutex_unlock(&pm_mutex);
return error;
}

4.1准备阶段,为进入supend状态做准备

static int suspend_prepare(void)
{
int error;
if (!suspend_ops || !suspend_ops->enter)
return -EPERM;
//allocate a console
pm_prepare_console();
//Run suspend notifiers
error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
if (error)
goto Finish;
error = usermodehelper_disable();
if (error)
goto Finish;
//进程处理
error = suspend_freeze_processes();
if (!error)
return 0;
suspend_thaw_processes();
usermodehelper_enable();
Finish:
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
return error;
}

4.2 进入阶段,挂起设备。

int suspend_devices_and_enter(suspend_state_t state)
{
int error;
if (!suspend_ops)
return -ENOSYS;
if (suspend_ops->begin) {
error = suspend_ops->begin(state);
if (error)
goto Close;
}
suspend_console();
suspend_test_start();
//挂起设备
error = dpm_suspend_start(PMSG_SUSPEND);
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend\n");
goto Recover_platform;
}
suspend_test_finish("suspend devices");
if (suspend_test(TEST_DEVICES))
goto Recover_platform;

suspend_enter(state);

Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
resume_console();
Close:
if (suspend_ops->end)
suspend_ops->end();
return error;

Recover_platform:
if (suspend_ops->recover)
suspend_ops->recover();
goto Resume_devices;
}

4.2.1挂起设备

int dpm_suspend_start(pm_message_t state)
{
int error;
might_sleep();
error = dpm_prepare(state);
if (!error)
error = dpm_suspend(state);
return error;
}

函数dpm_suspend_start()最后会调用dpm_suspend函数来挂起每个设备。

static int dpm_suspend(pm_message_t state)
{
struct list_head list;
int error = 0;
INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
//遍历设备链表, 当一个设备被注册进系统时, 它同时会被加入到这个dpm_list队列中
while (!list_empty(&dpm_list)) {
struct device *dev = to_device(dpm_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
//挂起这个设备
error = suspend_device(dev, state);
mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, "", error);
put_device(dev);
break;
}
dev->power.status = DPM_OFF;
//加入list队列, 用于以后唤醒
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &list);
put_device(dev);
}
list_splice(&list, dpm_list.prev);
mutex_unlock(&dpm_list_mtx);
return error;
}

此函数又会调用 suspend_device()函数挂起相应得设备,这个函数调用相应设备的suspend实现设备挂起. 所以说, 系统挂起时, 设备也应该做相应的工作, 由于设备的特殊性, 这些就是留在设备里面来实现了.

static int suspend_device(struct device *dev, pm_message_t state)
{
int error = 0;
down(&dev->sem);
if (dev->class) {
if (dev->class->pm) {
pm_dev_dbg(dev, state, "class ");
error = pm_op(dev, dev->class->pm, state);
} else if (dev->class->suspend) {
pm_dev_dbg(dev, state, "legacy class ");
error = dev->class->suspend(dev, state);
suspend_report_result(dev->class->suspend, error);
}
if (error)
goto End;
}
if (dev->type) {
if (dev->type->pm) {
pm_dev_dbg(dev, state, "type ");
error = pm_op(dev, dev->type->pm, state);
} else if (dev->type->suspend) {
pm_dev_dbg(dev, state, "legacy type ");
error = dev->type->suspend(dev, state);
suspend_report_result(dev->type->suspend, error);
}
if (error)
goto End;
}
if (dev->bus) {
if (dev->bus->pm) {
pm_dev_dbg(dev, state, "");
error = pm_op(dev, dev->bus->pm, state);
} else if (dev->bus->suspend) {
pm_dev_dbg(dev, state, "legacy ");
error = dev->bus->suspend(dev, state);
suspend_report_result(dev->bus->suspend, error);
}
}
End:
up(&dev->sem);
return error;
}

4.2.2 进入阶段

回到suspend_devices_and_enter()再看suspend_enter(state)函数
static int suspend_enter(suspend_state_t state)
{
int error;
//调用体系结构相关的函数, 这是在系统初始化的时候注册的
if (suspend_ops->prepare) {
error = suspend_ops->prepare();
if (error)
return error;
}

error = device_power_down(PMSG_SUSPEND);
if (error) {
printk(KERN_ERR "PM: Some devices failed to power down\n");
goto Platfrom_finish;
}
if (suspend_ops->prepare_late) {
error = suspend_ops->prepare_late();
if (error)
goto Power_up_devices;
}
if (suspend_test(TEST_PLATFORM))
goto Platform_wake;
error = disable_nonboot_cpus();
if (error || suspend_test(TEST_CPUS))
goto Enable_cpus;
arch_suspend_disable_irqs();
BUG_ON(!irqs_disabled());
error = sysdev_suspend(PMSG_SUSPEND);
if (!error) {
if (!suspend_test(TEST_CORE))
//调用体系结构相关的函数, 这是在系统初始化的时候注册的
error = suspend_ops->enter(state);
sysdev_resume();
}
arch_suspend_enable_irqs();
BUG_ON(irqs_disabled());

Enable_cpus:
enable_nonboot_cpus();
Platform_wake:
if (suspend_ops->wake)
suspend_ops->wake();
Power_up_devices:
device_power_up(PMSG_RESUME);
Platfrom_finish:
if (suspend_ops->finish)
//调用体系结构相关的函数, 这是在系统初始化的时候注册的
suspend_ops->finish();
return error;
}
这个suspend_ops就是在体系结构初始化的时候注册进来的,
接着看arch/arm/plat-s3c/pm.c
int __init s3c_pm_init(void)
{
printk("S3C Power Management, Copyright 2004 Simtec Electronics\n");
suspend_set_ops(&s3c_pm_ops);
return 0;
}

static struct platform_suspend_ops s3c_pm_ops = {
.enter = s3c_pm_enter,
.prepare = s3c_pm_prepare,
.finish = s3c_pm_finish,
.valid = suspend_valid_only_mem,
};
这就是实现三个状态转换的三个钩子函数.
这个函数较为简单, 只是将/kerenel/power/main.c里的全局变量suspend_ops设置成
s3c_pm_ops而已了.
这就完成了这个全局变量的初始化.后续对suspend_ops的访问实质上都是访问s3c_pm_ops.
void suspend_set_ops(struct platform_suspend_ops *ops)
{
mutex_lock(&pm_mutex);
suspend_ops = ops;
mutex_unlock(&pm_mutex);
}
最后看看函数实现:
static int s3c_pm_prepare(void)
{
/* prepare check area if configured */
s3c_pm_check_prepare();
return 0;
}

static void s3c_pm_finish(void)
{
s3c_pm_check_cleanup();
}
这里主要是s3c_pm_nter函数:
static int s3c_pm_enter(suspend_state_t state)
{
//用于保存16个通用寄存器的栈
static unsigned long regs_save[16];
/* ensure the debug is initialised (if enabled) */
s3c_pm_debug_init();
S3C_PMDBG("%s(%d)\n", __func__, state);
if (pm_cpu_prep == NULL || pm_cpu_sleep == NULL) {
printk(KERN_ERR "%s: error: no cpu sleep function\n", __func__);
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 "%s: No wake-up sources!\n", __func__);
printk(KERN_ERR "%s: Aborting sleep\n", __func__);
return -EINVAL;
}

/* store the physical address of the register recovery block */
//寄存器的物理地址
s3c_sleep_save_phys = virt_to_phys(regs_save);
S3C_PMDBG("s3c_sleep_save_phys=0x%08lx\n", s3c_sleep_save_phys);
//保存不属于driver的核心寄存器, driver的各自保存
s3c_pm_save_gpios();
s3c_pm_save_uarts();
s3c_pm_save_core();
/* set the irq configuration for wake */
//设置外部中断用于唤醒
s3c_pm_configure_extint();
S3C_PMDBG("sleep: irq wakeup masks: %08lx,%08lx\n",
s3c_irqwake_intmask, s3c_irqwake_eintmask);
s3c_pm_arch_prepare_irqs();
/* call cpu specific preparation */
pm_cpu_prep();
/* flush cache back to ram */
flush_cache_all();
s3c_pm_check_store();
/* send the cpu to sleep... */
s3c_pm_arch_stop_clocks();
/* s3c_cpu_save will also act as our return point from when
* we resume as it saves its own register state and restores it
* during the resume. */
s3c_cpu_save(regs_save);
/* restore the cpu state using the kernel's cpu init code. */
//当接收到一个外部中断时,系统开始恢复
cpu_init();
/* restore the system state */
s3c_pm_restore_core();
s3c_pm_restore_uarts();
s3c_pm_restore_gpios();
s3c_pm_debug_init();
/* check what irq (if any) restored the system */
s3c_pm_arch_show_resume_irqs();
S3C_PMDBG("%s: post sleep, preparing to return\n", __func__);
s3c_pm_check_restore();
/* ok, let's return from sleep */
S3C_PMDBG("S3C PM Resume (post-restore)\n");
return 0;
}

阅读(1254) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~