zoukankan      html  css  js  c++  java
  • Linux内核 runtime_PM 框架

    runtime PM (runtime power management) 简介:

    怎样动态地打开关闭设备的电源 ? 最简单的方法:在驱动程序中,open时打开电源,在close时关闭电源。但是有一个缺点,当多个App使用该设备时可能造成干扰。
    解决方法:给驱动添加计数值,当该值大于0时打开电源,等于0时关闭电源。

    多在ioctl中进行控制,例如alsa的驱动代码

    runtime PM只是提供辅助函数,比如:
    (1).增加计数/减少计数
    (2).使能runtime pm

    最好的资料是runtime_pm.txt  TODO:翻译它

    例子:driversinputmiscma150.c
    pm_runtime_enable //bma150_probe
    pm_runtime_disable //bma150_remove
    pm_runtime_get_sync //bma150_open
    pm_runtime_put_sync //bma150_close

    pm_runtime_enable/pm_runtime_disable 使能/禁止runtime PM,分别对dev->power.disable_depth执行++和--操作,这个变量的初始化值是1,默认是disable的状态
    pm_runtime_get_sync/pm_runtime_put_sync 增加/减少计数值,并判断是否进入suspend/resume。

    1. 在struct dev_pm_ops提供了3个回调函数:runtime_suspend,runtime_resume,runtime_idle,一般runtime_idle这个空闲函数不需要提供

    2. 上面4个函数不会直接导致runtime_suspend,runtime_resume,runtime_idle被调用,只是使能和修改计数值,当引用计数减为0,调用suspend,
    从0变为大于0调用resume

    3. 对于runtime PM,默认状态下设备的状态是suspend,如果硬件上它是运行状态,需要调用pm_runtime_set_active()来修改它的状态,然后调用
    pm_runtime_enable()来使能runtime PM。一般是在probe()的结尾处使用,以为它可能导致runtime的suspend/resume函数立即调用。一般在驱动remove中
    调用pm_runtime_disable()

    4. 在open()/release()接口中可以调用pm_runtime_get_sync/pm_runtime_put_sync

    5. autosuspend:
    为了不想让设备频繁地开、关,可以使用autosuspend功能,驱动中执行update_autosuspend()来启用autosuspend功能。[TODO]
    put设备时换做执行:
    pm_runtime_mark_last_busy()
    pm_runtime_put_sync_autosuspend()
    用户空间可以通过设置下面sysfs文件来设置autosuspend延迟时间:
    ehco 2000 > /sys/devices/.../power/autosuspend_delay_ms

    6. struct dev_pm_ops 注解翻译:
    用于定义要在所有情况下使用的一组PM操作(如系统挂起,休眠或运行时PM)。 注意:通常,系统挂起回调.suspend 和.resume 应该与
    对应的运行时PM回调.runtime_suspend 和.runtime_resume 不同,因为.runtime_suspend 始终适用于已经暂停的设备,而.suspend 应
    该假设在调用它时设备可能正在做某事(它应该确保设备在它返回后可靠地处于暂停状态)。 因此,最好将“late” suspend 和“early”resume
    回调指针.suspend_late和.resume_early分别指向与.runtime_suspend和.runtime_resume相同的例程(类似于休眠)。

    7.流程分析:

    pm_runtime_get_sync
        __pm_runtime_resume(dev, RPM_GET_PUT) 
            atomic_inc(&dev->power.usage_count); // 若上级arg2&RPM_GET_PUT为真,才调用
            rpm_resume(dev, rpmflags) //关闭本地CPU中断后调用它
                if (dev->power.disable_depth > 0) retval = -EACCES; //若要使用runtime PM的函数,需要首先pm_runtime_enable。
                if (!dev->power.timer_autosuspends) /*为了防止设备频繁的开关,可以设置timer_autosuspends的值*/
                    pm_runtime_deactivate_timer(dev);
                if (dev->power.runtime_status == RPM_ACTIVE) {  /*如果已经是ACTIVE,就没有必要再次resume*/
                if (dev->power.runtime_status == RPM_RESUMING || dev->power.runtime_status == RPM_SUSPENDING) 如果设备正处于RPM_RESUMING和RPM_SUSPENDING状态,等待其完成
                if (!parent && dev->parent) //增加父级的使用计数器并在必要时恢复它,在resume设备本身之前先resume父设备。
                开始resume设备自己:
                    dev->pm_domain->ops->runtime_resume    //
                    dev->type->pm->runtime_resume          //
                    dev->class->pm->runtime_resume         //
                    dev->bus->pm->runtime_resume           //或 前4个被称为subsystem level的callback,优先调用,第5个是驱动级别的。
                    dev->driver->pm->runtime_resume        //
                __update_runtime_status(dev, RPM_SUSPENDED); //如果resume失败,重新设置回SUSPENDED状态
                if (parent) atomic_inc(&parent->power.child_count); //如果resume成功时给父亲的child_count加1
                
                wake_up_all(&dev->power.wait_queue); //唤醒其它进程
                
    
    pm_runtime_put_sync
        __pm_runtime_idle(dev, RPM_GET_PUT)
            if (!atomic_dec_and_test(&dev->power.usage_count)) //减少usage_count引用计数
            rpm_idle(dev, rpmflags); //让设备进入idle状态
                rpm_check_suspend_allowed //检查是否允许设备进入suspend状态,看来内核把idle和suspend一样看待了。
                    if (dev->power.disable_depth > 0) retval = -EACCES; //调用时还没有pm_runtime_enable,就失败。
                    if (atomic_read(&dev->power.usage_count) > 0) retval = -EAGAIN;
                    if (!dev->power.ignore_children && atomic_read(&dev->power.child_count)) retval = -EBUSY; //它的孩子不全睡眠它是不能睡眠的
                if (dev->power.runtime_status != RPM_ACTIVE) retval = -EAGAIN; //如果不是出于ACTIVE状态直接返回。
                开始suspend设备自己:
                dev->pm_domain->ops->runtime_suspend    //
                dev->type->pm->runtime_suspend          //
                dev->class->pm->runtime_suspend         //
                dev->bus->pm->runtime_suspend           //或 前4个被称为subsystem level的callback,优先调用,第5个是驱动级别的。
                dev->driver->pm->runtime_suspend        //
            wake_up_all(&dev->power.wait_queue); //唤醒其它进程

    8. 将当前进程放入等待队列中睡眠

    rpm_resume():

    DEFINE_WAIT(wait);

    for (;;) {
      prepare_to_wait(&dev->power.wait_queue, &wait, TASK_UNINTERRUPTIBLE);

      if (dev->power.runtime_status != RPM_RESUMING && dev->power.runtime_status != RPM_SUSPENDING)
        break;

      schedule();
    }
    finish_wait(&dev->power.wait_queue, &wait);

    9. 另外,额外说一下异步实现原理

    request_firmware_nowait()
        INIT_WORK(&fw_work->work, request_firmware_work_func);
        schedule_work(&fw_work->work);

    10. 如何使用runtime PM
    (1). 驱动封装接口,App去调用,如把pm_runtime_get_sync放在open()中。
    (2). 通过sysfs接口使用:
    操作 /sys/devices/.../power/control 导致 drivers/base/power/sysfs.c/control_store() 被调用。对自己的驱动设置了runtime PM 操作后也可以使用
    这种操作来测试自己的runtime PM 中的suspend/resume。

    //App 禁止驱动程序对设备进行runtime PM
    echo auto > /sys/devices/.../power/control:control_store --> pm_runtime_forbid --> 
                                                atomic_inc(&dev->power.usage_count);
                                                rpm_resume(dev, 0);
    //App 允许驱动程序对设备进行runtime PM
    echo on > /sys/devices/.../power/control:control_store --> pm_runtime_allow -->
                                                if (atomic_dec_and_test(&dev->power.usage_count))
                                                    rpm_idle(dev, RPM_AUTO | RPM_ASYNC);
    
    可以echo on > /sys/devices/.../power/control  //来启用一个休眠的设备

    unsigned int runtime_auto;
    - 如果被设置了,则表示用户空间允许设备驱动程序通过/sys/devices/.../power/control接口在运行时为设备供电; 它只能在pm_runtime_allow()
    和pm_runtime_forbid()辅助函数的帮助下修改.

    用户空间可以通过将其/sys/devices/.../power/control属性的值更改为“on”来有效地禁止设备的驱动程序在运行时对设备进行电源管理,
    这会导致调用pm_runtime_forbid()。
    原则上,驱动程序还可以使用该机制来有效地关闭设备的运行时电源管理,直到用户空间将其打开为止。 即,在初始化期间,驱动程序可
    以确保设备的运行时PM状态为“活动”并调用pm_runtime_forbid()。
    但是,应该注意的是,如果用户空间已经故意将/sys/devices/.../power/control的值更改为“auto”以允许驱动程序在运行时对设备进行电
    源管理,则驱动程序这样使用pm_runtime_forbid()可能会导致混淆。

    11. 修改驱动程序使用runtime PM, 可以参考:driversinputmiscma150.c

    /*不使用autosuspend的电源管理框架:*/
    
    static struct platform_device lcd_dev;
    
    
    /* 提供给用户的电源管理框架 */
    static int mylcd_open(struct fb_info *info, int user)
    {
        pm_runtime_get_sync(&lcd_dev.dev);
        return 0;
    }
    static int mylcd_release(struct fb_info *info, int user)
    {
        pm_runtime_put_sync(&lcd_dev.dev);
        return 0;
    }
    
    /* suspend和resume的通知,见第一篇博客 */
    static int lcd_suspend_notifier(struct notifier_block *nb,
                    unsigned long event,
                    void *dummy)
    {
    
        switch (event) {
        case PM_SUSPEND_PREPARE:
            printk("lcd suspend notifiler test: PM_SUSPEND_PREPARE
    ");
            return NOTIFY_OK;
        case PM_POST_SUSPEND:
            printk("lcd suspend notifiler test: PM_POST_SUSPEND
    ");
            return NOTIFY_OK;
    
        default:
            return NOTIFY_DONE;
        }
    }
    
    static struct notifier_block lcd_pm_notif_block = {
        .notifier_call = lcd_suspend_notifier,
    };
    
    static void lcd_release(struct device * dev)
    {
    }
    
    static struct platform_device lcd_dev = {
        .name         = "mylcd",
        .id       = -1,
        .dev = {
            .release = lcd_release,
        },
    };
    static int lcd_probe(struct platform_device *pdev)
    {
        pm_runtime_set_active(&pdev->dev);
        pm_runtime_enable(&pdev->dev);
        return 0;
    }
    static int lcd_remove(struct platform_device *pdev)
    {
        pm_runtime_disable(&pdev->dev);
        return 0;
    }
    static int lcd_suspend(struct device *dev)
    {
        int i;
        unsigned long *dest = &lcd_regs_backup;
        unsigned long *src  = lcd_regs;
    
        /* 1.保存寄存器状态 */
        for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
        {
            dest[i] = src[i];
        }
    
        /* 2.断设备的电 */
        lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
        *gpbdat &= ~1;     /* 关闭背光 */
        return 0;
    }
    
    static int lcd_resume(struct device *dev)
    {
        int i;
        unsigned long *dest = lcd_regs;
        unsigned long *src  = &lcd_regs_backup;
    
        /* 1.还原到掉电之前的状态 */
        struct clk *clk = clk_get(NULL, "lcd");
        clk_enable(clk);
        clk_put(clk);
        for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
        {
            dest[i] = src[i];
        }
    
        /* 2.重新上电*/
        lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
        lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
        *gpbdat |= 1;     /* 输出高电平, 使能背光 */
        return 0;
    }
    
    static struct dev_pm_ops lcd_pm = {
        .suspend = lcd_suspend,
        .resume  = lcd_resume,
        .runtime_suspend = lcd_suspend,
        .runtime_resume  = lcd_resume,
    };
    
    struct platform_driver lcd_drv = {
        .probe        = lcd_probe,
        .remove        = lcd_remove,
        .driver        = {
            .name    = "mylcd",
            .pm     = &lcd_pm,
        }
    };
    
    static int lcd_init(void)
    {
        /* 电源管理 */
        register_pm_notifier(&lcd_pm_notif_block); /*一注册就可能导致电源runtime函数立即被调用*/
    
        platform_device_register(&lcd_dev);
        platform_driver_register(&lcd_drv);
    
        return 0;
    }
    
    static void lcd_exit(void)
    {
        unregister_pm_notifier(&lcd_pm_notif_block);
        platform_device_unregister(&lcd_dev);
        platform_driver_unregister(&lcd_drv);
    }
    
    module_init(lcd_init);
    module_exit(lcd_exit);
    
    MODULE_LICENSE("GPL");
    /*不使用autosuspend的电源管理框架:*/
    
    static struct platform_device lcd_dev;
    
    static int mylcd_open(struct fb_info *info, int user)
    {
        pm_runtime_get_sync(&lcd_dev.dev);
        return 0;
    }
    static int mylcd_release(struct fb_info *info, int user)
    {
        pm_runtime_mark_last_busy(&lcd_dev.dev);
        pm_runtime_put_sync_autosuspend(&lcd_dev.dev);
        return 0;
    }
    
    static int lcd_suspend_notifier(struct notifier_block *nb,
                    unsigned long event,
                    void *dummy)
    {
    
        switch (event) {
        case PM_SUSPEND_PREPARE:
            printk("lcd suspend notifiler test: PM_SUSPEND_PREPARE
    ");
            return NOTIFY_OK;
        case PM_POST_SUSPEND:
            printk("lcd suspend notifiler test: PM_POST_SUSPEND
    ");
            return NOTIFY_OK;
    
        default:
            return NOTIFY_DONE;
        }
    }
    
    static struct notifier_block lcd_pm_notif_block = {
        .notifier_call = lcd_suspend_notifier,
    };
    
    static void lcd_release(struct device * dev)
    {
    }
    
    static struct platform_device lcd_dev = {
        .name         = "mylcd",
        .id       = -1,
        .dev = {
            .release = lcd_release,
        },
    };
    static int lcd_probe(struct platform_device *pdev)
    {
        /* 因为runtime PM 默认上电是关闭的,而这个设备默认上电就是使用的 */
        pm_runtime_set_active(&pdev->dev);
        pm_runtime_use_autosuspend(&pdev->dev);
        pm_runtime_enable(&pdev->dev);
        return 0;
    }
    static int lcd_remove(struct platform_device *pdev)
    {
        pm_runtime_disable(&pdev->dev);
        return 0;
    }
    static int lcd_suspend(struct device *dev)
    {
        int i;
        unsigned long *dest = &lcd_regs_backup;
        unsigned long *src  = lcd_regs;
    
        for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
        {
            dest[i] = src[i];
        }
    
        lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
        *gpbdat &= ~1;     /* 关闭背光 */
        return 0;
    }
    
    static int lcd_resume(struct device *dev)
    {
        int i;
        unsigned long *dest = lcd_regs;
        unsigned long *src  = &lcd_regs_backup;
    
        struct clk *clk = clk_get(NULL, "lcd");
        clk_enable(clk);
        clk_put(clk);
    
        for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
        {
            dest[i] = src[i];
        }
    
        lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
        lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
        *gpbdat |= 1;     /* 输出高电平, 使能背光 */
        return 0;
    }
    
    static struct dev_pm_ops lcd_pm = {
        .suspend = lcd_suspend,
        .resume  = lcd_resume,
        .runtime_suspend = lcd_suspend,
        .runtime_resume  = lcd_resume,
    };
    
    struct platform_driver lcd_drv = {
        .probe        = lcd_probe,
        .remove        = lcd_remove,
        .driver        = {
            .name    = "mylcd",
            .pm     = &lcd_pm,
        }
    };
    
    
    static int lcd_init(void)
    {
        /* 电源管理 */
        register_pm_notifier(&lcd_pm_notif_block);
    
        platform_device_register(&lcd_dev);
        platform_driver_register(&lcd_drv);
    
        return 0;
    }
    
    static void lcd_exit(void)
    {
        unregister_pm_notifier(&lcd_pm_notif_block);
        platform_device_unregister(&lcd_dev);
        platform_driver_unregister(&lcd_drv);
    }
    
    module_init(lcd_init);
    module_exit(lcd_exit);
    
    MODULE_LICENSE("GPL");
  • 相关阅读:
    Kali Linux下安装配置ProFTPD实例
    mysql 如何用root 登录
    串口总是报'Error opening serial port'
    用SPCOMM 在 Delphi中实现串口通讯 转
    delphi中使用spcomm来实现串口通讯(转载)
    SPCOMM的一些用法注意
    MySQL 字符串 转 int/double CAST与CONVERT 函数的用法
    彻底删除mysql服务
    mysql 非安装版的一个自动安装脚本及工具(更新版)
    bat操作数据库mysql
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/9974273.html
Copyright © 2011-2022 走看看