arm电源管理(1)--apm.c
R.wen
由于arm系统中没有bios设备, 所以只能为arm系统创建一个虚拟的字符设备与用户空间进行通讯.
这就是/arch/arm/kernel/amp.c
1. 工作原理:
这个apm中实现一个misc设备,实质上也是一个字符设备, misc设备的主设备号是10, 而apm_bios作为一
个misc设备, 次设备号是134. 定义为:
/*
* 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状态.
2. 初始化
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;
//创建一个线程, 用于处理事件队列, 工作函数是kapmd
//这个线程好像在arm中没有作用? ret = kernel_thread(kapmd, NULL, CLONE_KERNEL);
if (ret < 0) {
pm_active = 0;
return ret;
}
//通过proc向用户空间输出apm信息
#ifdef CONFIG_PROC_FS
create_proc_info_entry("apm", 0, NULL, apm_get_info);
#endif
//注册misc设备
ret = misc_register(&apm_device);
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
};
3. 结构函数的实现
当一个用户进程打开apm_bios设备时, 它就会调用这个函数
static int apm_open(struct inode * inode, struct file * filp)
{
struct apm_user *as;
//分配一个apm_user结构, 来表示一个用户进程
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;
}
当用户空间进程去读这个设备时, 这个函数就会被调用.
这个函数的主要作用是将事件读出到用户空间
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;
//设置状态
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;
}
//这个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;
}
//这个是这个设备的核心函数, 用于内核与用户空间交互
/*
* apm_ioctl - handle APM ioctl
*
* APM_IOC_SUSPEND
* This IOCTL is overloaded, and performs two functions. It is used to:
* - initiate a suspend
* - acknowledge a suspend read from /dev/apm_bios.
* Only when everyone who has opened /dev/apm_bios with write permission
* has acknowledge does the actual suspend happen.
*/
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) {
/*
* 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(); //系统进入suspend状态
//从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);
current->flags = flags;
err = as->suspend_result;
as->suspend_state = SUSPEND_NONE;
break;
}
return err;
}
4. 事件队列函数
static void queue_event(apm_event_t event, struct apm_user *sender)
{
struct apm_user *as;
down_read(&user_list_lock);
//将事件加入其他USER,除自己外
list_for_each_entry(as, &apm_user_list, list) {
if (as != sender && as->reader)
queue_event_one_user(as, event);
}
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);
}
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; 加入队头
}
在来看一个出队的函数:
//在队尾出列
static inline apm_event_t queue_get_event(struct apm_queue *q)
{
q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
return q->events[q->event_tail];
}
/*
* APM event queue management.
*/
static inline int queue_empty(struct apm_queue *q)
{
return q->event_head == q->event_tail;
}
//队列的结构
/*
* Maximum number of events stored
*/
#define APM_MAX_EVENTS 16
struct apm_queue {
unsigned int event_head;
unsigned int event_tail;
apm_event_t events[APM_MAX_EVENTS];
};
5. apm_suspend()
这里才是整个设备想做的事情--将系统转入suspend状态
static void apm_suspend(void)
{
struct apm_user *as;
//调用体系无关的接口,将系统转入suspend状态
int err = pm_suspend(PM_SUSPEND_MEM);
//从suspend返回
/*
* Anyone on the APM queues will think we're still suspended.
* Send a message so everyone knows we're now awake again.
*/
//发送一个resume事件
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; //suspend完成
}
up_read(&user_list_lock);
//唤醒睡眠进程
wake_up(&apm_suspend_waitqueue);
}
http://hi.baidu.com/rwen2012/blog/item/b605443b37bfe2e915cecbc8.html