puppy居
puppy居士
posts - 41,comments - 27,trackbacks - 0
s3c2410 电源管理(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);
}
posted on 2008-08-22 15:01 puppy 阅读(544) 评论(0)  编辑 收藏 引用
只有注册用户登录后才能发表评论。