Linux PM domain framework(1)_概述和使用流程

作者:wowo 发布于:2014-11-13 22:09 分类:电源管理子系统

1. 前言

在复杂的片上系统(SOC)中,设计者一般会将系统的供电分为多个独立的block,这称作电源域(Power Domain),这样做有很多好处,例如:

1)将不同功能模块的供电分开,减小相互之间的干扰(如模拟和数字分开)。

2)不同功能所需的电压大小不同:小电压能量损耗低,但对信号质量的要求较高;大电压能量损耗高,对信号质量的要求较低。因此可以根据实际情况,使用不同的电压供电,例如CPU core只需1.2v左右即可,而大部分的I/O则需要3.3v左右。

3)系统运行的大部分时间,并不需要所有模块都处于power on状态,因此可以通过关闭不工作模块的供电,将它们的耗电降为最低。

4)等等

虽然电源域的好处多多,却不是越多越好,因为划分电源域是需要成本的(需要在PMU中使用模拟电路完成,包括金钱成本和空间成本)。因此,大多数系统会根据功能,设置有限的几个电源域,例如:CPU core(1、2、3…);GPU;NAND;DDR;USB;Display;Codec;等等。

这种设计引出一个问题:存在多个模块共用一个电源域的情况。因而要求在对模块power on/off的时候,考虑power共用的情况:只要一个模块工作,就要power on;直到所有模块停止工作,才能power off。

Kernel的PM domain framework(位于drivers/base/power/domain.c中),提供了管理和使用系统power domain的统一方法,在解决上面提到的问题的同时,结合kernel的suspendruntime pmclock framework等机制,以非常巧妙、灵活的方式,管理系统供电,以达到高效、节能的目的。

同样,作为一个framework,我们可以从三个角度分析:使用者(consumer)的角度;提供者(provider)的角度;内部实现。具体如下。

注:本文的linux kernel版本为3.18-rc4。一般情况下,对于那些相对稳定的framework,蜗蜗不会说明文章所使用的kernel版本,但本文是个例外,因为PM domain很多方便、易用的patch,只能在最新版本(当前为3.18-rc4)kernel上才能看到。

2. 怎么使用(从consumer的角度看)

借助device tree,pm domain的使用很简单(非常类似clock的使用,详见“clock framework的分析文章”),步骤如下:

1)检查pm domain provider提供的DTS的node名(下面的红色字体):

powergate: power-domain@e012e000 {

        compatible = "xxx,xxx-pm-domains";

        reg = <0 0xe012e000 0 0x1000>;

        #power-domain-cells = <1>;

};

各个字段和clock framework类似,也可参考“Documentation/devicetree/bindings/power/power_domain.txt”,这里不再详细描述。

2)大部分情况下,power-domain-cells为1,因此需要得到所需使用的power domain的编号(可能会在include/dt-bindings/*中定义),如POWER_DOMAIN_USB。

3)在模块自己的DTS中,添加对power domian的引用描述,如下面红色字体:

power-domain-example@e028e000 {

        compatible = "xxx,xxx-dummy";

        reg = <0 0xe0280000 0 0x1000>;

        power-domains = <&powergate POWER_DOMAIN_USB>;

};

其中:“power-domains”为pm domain framework的关键字,由framework core负责解析(由名称可知,可以指定多个power domain);“&powergate”为provider提供的DTS node名;“POWER_DOMAIN_USB”具体的domain标识,也是由provider提供。

4)借助runtime pm,在需要使用模块时,增加引用计数(可调用pm_runtime_get),不需要使用时,减少引用计数(可调用pm_runtime_put)。剩下的事情,就交给PM core了。

注2:PM core会在设备的引用计数为0时,自动调用PM domain的接口,尝试power off设备。同理,会在引用计数从0到1时,尝试power on设备。整个过程不需要设备的driver关心任何细节。同时,runtime pm也会处理idle、suspend、resume等相关的电源状态切换,如果driver只想使用PM domain功能,可以在probe中get一次,在remove中put一次,其效果和常规的power on/power off类似。

3. 怎么编写PM domain驱动(从provider的角度看)

从接口层面看,编写PM domain driver相当简单,只需要三个步骤:

1)将所有的domain,以struct generic_pm_domain(PM domain framework提供的)形式抽象出来,并填充数据结构中需要由provider提供的内容。

2)调用pm_genpd_init,初始化struct generic_pm_domain变量中其余的内容。

3)调用__of_genpd_add_provider接口,将所有的domain(由struct generic_pm_domain变量抽象)添加到kernel中,同时提供一个根据DTS node获得对应的domain指针的回调函数(类似clock framework)。

很显然,这三个步骤对我们编写pm domain driver没有任何帮助,因为其复杂度都被掩盖在struct generic_pm_domain结构体中了。让我们分析完内部逻辑后,再回来。

4. 基本流程分析

4.1 一些数据结构

1)我们先回到“Linux设备模型(5)_device和device driver”中,那篇文章我们留下了很多“未解之谜”,其中之一就是pm_domain指针,如下:

   1:  
   2: struct device {
   3:     ...
   4:     struct dev_pm_domain    *pm_domain;
   5:     ...
   6: };

struct dev_pm_domain结构很简单,只有一个struct dev_pm_ops类型的变量,该结构在“Linux电源管理(4)_Power Management Interface”中已有详细描述,是设备电源管理相关的操作函数集,包括idle、suspend/resume、runtime pm等有关的回调函数。

那这个结构和PM domain有什么关系呢?不着急,先看看struct generic_pm_domain结构。

2)struct generic_pm_domain

struct generic_pm_domain结构用于抽象PM domain,在include/linux/pm_domain.h中定义,如下:

   1: /* include/linux/pm_domain.h */
   2: struct generic_pm_domain {
   3:         struct dev_pm_domain domain;    /* PM domain operations */
   4:         struct list_head gpd_list_node; /* Node in the global PM domains list */
   5:         struct list_head master_links;  /* Links with PM domain as a master */
   6:         struct list_head slave_links;   /* Links with PM domain as a slave */
   7:         struct list_head dev_list;      /* List of devices */
   8:         struct mutex lock;
   9:         struct dev_power_governor *gov;
  10:         struct work_struct power_off_work;
  11:         const char *name;
  12:         unsigned int in_progress;       /* Number of devices being suspended now */
  13:         atomic_t sd_count;      /* Number of subdomains with power "on" */
  14:         enum gpd_status status; /* Current state of the domain */
  15:         wait_queue_head_t status_wait_queue;
  16:         struct task_struct *poweroff_task;      /* Powering off task */
  17:         unsigned int resume_count;      /* Number of devices being resumed */
  18:         unsigned int device_count;      /* Number of devices */
  19:         unsigned int suspended_count;   /* System suspend device counter */
  20:         unsigned int prepared_count;    /* Suspend counter of prepared devices */
  21:         bool suspend_power_off; /* Power status before system suspend */
  22:         int (*power_off)(struct generic_pm_domain *domain);
  23:         s64 power_off_latency_ns;
  24:         int (*power_on)(struct generic_pm_domain *domain);
  25:         s64 power_on_latency_ns;
  26:         struct gpd_dev_ops dev_ops;
  27:         s64 max_off_time_ns;    /* Maximum allowed "suspended" time. */
  28:         bool max_off_time_changed;
  29:         bool cached_power_down_ok;
  30:         struct gpd_cpuidle_data *cpuidle_data;
  31:         void (*attach_dev)(struct device *dev);
  32:         void (*detach_dev)(struct device *dev);
  33: };

这个结构很复杂,包括很多参数,不过:对consumer来说,不需要关心该结构;对provider而言,只需要关心有限的参数;大部分参数,framework内部使用。

对provider来说,需要为每个power domain定义一个struct generic_pm_domain变量,并至少提供如下字段:

name,该power domain的名称;

power_off/power_on,该power domain的on/off接口;

其它的字段,这里做一个大概的介绍(后续代码逻辑分析时会更为细致的说明):

domain,struct dev_pm_domain类型的变量,再回忆一下struct device中的pm_domain指针,这二者一定有些关系,后面再详细描述

gpd_list_node,用于将该domain添加到一个全局的domain链表(gpd_list)中;

master_links/slave_links,power domain可以有从属关系,例如一个power domain,通过一些器件,分出另外几个power domain,那么这个domain称作master domain,分出来的domain称作slave domain(也成subdomain)。这两个list用于组成master link和slave link,后面再详细描述;

dev_list,该domain下所有device的列表;

gov,struct dev_power_governor指针,后面再解释;

power_off_work,用于执行power off的wrok queue;

in_progress,该domain下正在suspend的device个数;

sd_count,记录处于power on状态的subdomain的个数;

status/status_wait_queue,power domain的状态,以及用于等待状态切换的wait queue;

power_off_task,

resume_count/device_count/suspended_count/prepared_count,

suspend_power_off,一个struct task_struct指针,记录正在执行power off操作的任务;

power_on_latency_ns/power_off_latency_ns,执行power on和power off操作时需要等待的时间,一般由provider提供;

dev_ops,struct gpd_dev_ops类型的变量,提供一些回调函数,后面再详细解释;

max_off_time_ns/max_off_timer_changed,和PM domain governor有关的变量,后面再详细解释;

cached_power_down_ok,同上;

cpuidle_data,可以把cpuidle和PM domain连接起来,这个指针用于保存CPU idle相关的数据;

attach_dev/detach_dev,当device和pm domain关联/去关联时,调用这两个回调函数。如果provider需要做一些处理,可以提供。

我相信读者一定被这个数据结构搞晕了,不要着急,太复杂的话,我们先放一下,去看一些简单的。下面一节我们先从整体流程上,看一下power domain framework怎么工作的(最简单的case),然后再回到这些细节上。

4.2 PM domain的工作流程

结合第二章、第三章的说明,PM domain的工作流程包括:

1)Provider在DTS中定义power domain有关的device tree node,并在provider的初始化接口(可以是一个platform driver的probe,也可以是其它形式)中,定义、初始化并注册所有的power domain。

2)PM domain的初始化和注册

provider需要为每个domain定义一个struct generic_pm_domain变量,并初始化必要的字段和回调函数,然后调用pm_genpd_init接口,初始化其余的字段,如下:

   1: /**
   2:  * pm_genpd_init - Initialize a generic I/O PM domain object.
   3:  * @genpd: PM domain object to initialize.
   4:  * @gov: PM domain governor to associate with the domain (may be NULL).
   5:  * @is_off: Initial value of the domain's power_is_off field.
   6:  */
   7: void pm_genpd_init(struct generic_pm_domain *genpd,
   8:            struct dev_power_governor *gov, bool is_off)
   9: {
  10:     if (IS_ERR_OR_NULL(genpd))
  11:         return;
  12:  
  13:     INIT_LIST_HEAD(&genpd->master_links);
  14:     INIT_LIST_HEAD(&genpd->slave_links);
  15:     INIT_LIST_HEAD(&genpd->dev_list);
  16:     mutex_init(&genpd->lock);
  17:     genpd->gov = gov;
  18:     INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn);
  19:     genpd->in_progress = 0;
  20:     atomic_set(&genpd->sd_count, 0);
  21:     genpd->status = is_off ? GPD_STATE_POWER_OFF : GPD_STATE_ACTIVE;
  22:     init_waitqueue_head(&genpd->status_wait_queue);
  23:     genpd->poweroff_task = NULL;
  24:     genpd->resume_count = 0;
  25:     genpd->device_count = 0;
  26:     genpd->max_off_time_ns = -1;
  27:     genpd->max_off_time_changed = true;
  28:     genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend;
  29:     genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume;
  30:     genpd->domain.ops.prepare = pm_genpd_prepare;
  31:     genpd->domain.ops.suspend = pm_genpd_suspend;
  32:     genpd->domain.ops.suspend_late = pm_genpd_suspend_late;
  33:     genpd->domain.ops.suspend_noirq = pm_genpd_suspend_noirq;
  34:     genpd->domain.ops.resume_noirq = pm_genpd_resume_noirq;
  35:     genpd->domain.ops.resume_early = pm_genpd_resume_early;
  36:     genpd->domain.ops.resume = pm_genpd_resume;
  37:     genpd->domain.ops.freeze = pm_genpd_freeze;
  38:     genpd->domain.ops.freeze_late = pm_genpd_freeze_late;
  39:     genpd->domain.ops.freeze_noirq = pm_genpd_freeze_noirq;
  40:     genpd->domain.ops.thaw_noirq = pm_genpd_thaw_noirq;
  41:     genpd->domain.ops.thaw_early = pm_genpd_thaw_early;
  42:     genpd->domain.ops.thaw = pm_genpd_thaw;
  43:     genpd->domain.ops.poweroff = pm_genpd_suspend;
  44:     genpd->domain.ops.poweroff_late = pm_genpd_suspend_late;
  45:     genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
  46:     genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq;
  47:     genpd->domain.ops.restore_early = pm_genpd_resume_early;
  48:     genpd->domain.ops.restore = pm_genpd_resume;
  49:     genpd->domain.ops.complete = pm_genpd_complete;
  50:     genpd->dev_ops.save_state = pm_genpd_default_save_state;
  51:     genpd->dev_ops.restore_state = pm_genpd_default_restore_state;
  52:     mutex_lock(&gpd_list_lock);
  53:     list_add(&genpd->gpd_list_node, &gpd_list);
  54:     mutex_unlock(&gpd_list_lock);
  55: }

该接口可接受三个参数:genpd为需要初始化的power domain指针;gov为governor,可以留空,我们先不考虑它;is_off,指明该power domain在注册时的状态,是on还是off,以便framework正确设置该domain的status字段。

它的逻辑比较简单,值得注意的是genpd->domain.ops中各个回调函数的初始化,这些以”pm_genqd_”为前缀的函数,都是pm domain framework提供的帮助函数,用于power domain级别的电源管理,包括power on/off、suspend/resume、runtime pm等。

回忆一下“Linux电源管理(4)_Power Management Interface”中有关struct dev_pm_ops的描述,bus_type、device_driver、class、device_type、device等结构,都可以包括dev pm ops,而PM core进行相关的电源状态切换时,只会调用其中的一个。选择哪个,是有优先顺序的,其中优先级最高的,就是device结构中pm_domain指针的。

那么,我们再思考一下,device中可是一个指针哦,具体的变量哪来的?你一定猜到了,来自struct generic_pm_domain变量,就是这个函数初始化的内容。后面再详细介绍。

3)完成所有domain的初始化后,调用__of_genpd_add_provider接口,将它们添加到kernel中。从该接口的命名上,我们可以猜到,它和DTS有关(of是Open Firmware的缩写),定义如下:

   1: /**
   2:  * __of_genpd_add_provider() - Register a PM domain provider for a node
   3:  * @np: Device node pointer associated with the PM domain provider.
   4:  * @xlate: Callback for decoding PM domain from phandle arguments.
   5:  * @data: Context pointer for @xlate callback.
   6:  */
   7: int __of_genpd_add_provider(struct device_node *np, genpd_xlate_t xlate,
   8:                         void *data)
   9: {
  10:         struct of_genpd_provider *cp;
  11:  
  12:         cp = kzalloc(sizeof(*cp), GFP_KERNEL);
  13:         if (!cp)
  14:                 return -ENOMEM;
  15:  
  16:         cp->node = of_node_get(np);
  17:         cp->data = data;
  18:         cp->xlate = xlate;
  19:  
  20:         mutex_lock(&of_genpd_mutex);
  21:         list_add(&cp->link, &of_genpd_providers);
  22:         mutex_unlock(&of_genpd_mutex);
  23:         pr_debug("Added domain provider from %s\n", np->full_name);
  24:  
  25:         return 0;
  26: }

也许您会奇怪,该接口的三个参数没有一个和struct generic_pm_domain所代表的power domain有关啊!不着急,我们慢慢分析。

参数1,np,是一个device node指针,哪来的?想一下第2章pm domain provider提供的DTS,就是它生成的指针;

参数2,xlate,一个用于解析power domain的回调函数,定义如下:

typedef struct generic_pm_domain *(*genpd_xlate_t)(struct of_phandle_args *args, void *data);

该回调函数的第二个参数,就是__of_genpd_add_provider接口的参数3。它会返回一个power domain指针;

参数3,data,一个包含了所有power domain信息的指针,具体的形式,由provider自行定义,反正最终会传给同样由provider提供的回调函数中,provider根据实际情况,获得对应的power domain指针,并返回给调用者。

注3:这是device tree的惯用伎俩,consumer在DTS中对所使用的资源(这里为power domain,如power-domains = <&powergate POWER_DOMAIN_USB>;)的声明,最终会由对应的framework(这里为pm domain framework)解析,并调用provider提供的回调函数,最终返回给consumer该资源的句柄(这里为一个struct generic_pm_domain指针)。所有的framework都是这样做的,包括前面所讲的clock framework,这里的pm domain framework,等等。具体的解析过程,后面会详细描述。

4)pm domain framework对consumer DTS中的power domain的解析

先把第2章的例子搬过来:

power-domain-example@e028e000 {

        …

        power-domains = <&powergate POWER_DOMAIN_USB>;

};

怎么解析呢?让我们从设备模型的platform_drv_probe接口开始。

Linux设备模型相关的文章可知,platform设备的枚举从platform_drv_probe开始。而所有在DTS中描述的设备,最终会生成一个platform设备,这个设备的driver的执行,也会从platform_drv_probe开始,该接口进而会调用platform driver的probe。如下:

   1: static int platform_drv_probe(struct device *_dev)
   2: {
   3:         struct platform_driver *drv = to_platform_driver(_dev->driver);
   4:         struct platform_device *dev = to_platform_device(_dev);
   5:         int ret;
   6:  
   7:         ret = of_clk_set_defaults(_dev->of_node, false);
   8:         if (ret < 0)
   9:                 return ret;
  10:  
  11:         ret = dev_pm_domain_attach(_dev, true);
  12:         if (ret != -EPROBE_DEFER) {
  13:                 ret = drv->probe(dev);
  14:                 if (ret)
  15:                         dev_pm_domain_detach(_dev, true);
  16:         }
  17:  
  18:         if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
  19:                 dev_warn(_dev, "probe deferral not supported\n");
  20:                 ret = -ENXIO;
  21:         }
  22:  
  23:         return ret;
  24: }

在执行driver的probe之前,会先调用dev_pm_domain_attach接口,将该设备attach到指定的power domain上(如果有的话)。该接口位于drivers/base/power/common.c中,实现如下:

   1: int dev_pm_domain_attach(struct device *dev, bool power_on)
   2: {
   3:         int ret;
   4:  
   5:         ret = acpi_dev_pm_attach(dev, power_on);
   6:         if (ret)
   7:                 ret = genpd_dev_pm_attach(dev);
   8:  
   9:         return ret;
  10: }
  11: EXPORT_SYMBOL_GPL(dev_pm_domain_attach);

先不考虑ACPI设备,会直接调用genpd_dev_pm_attach接口(呵呵,回到pm domain framework了),该接口位于drivers/base/power/domain.c,如下:

   1: /**
   2:  * genpd_dev_pm_attach - Attach a device to its PM domain using DT.
   3:  * @dev: Device to attach.
   4:  *
   5:  * Parse device's OF node to find a PM domain specifier. If such is found,
   6:  * attaches the device to retrieved pm_domain ops.
   7:  *
   8:  * Both generic and legacy Samsung-specific DT bindings are supported to keep
   9:  * backwards compatibility with existing DTBs.
  10:  *
  11:  * Returns 0 on successfully attached PM domain or negative error code.
  12:  */
  13: int genpd_dev_pm_attach(struct device *dev)
  14: {
  15:     struct of_phandle_args pd_args;
  16:     struct generic_pm_domain *pd;
  17:     int ret;
  18:  
  19:     if (!dev->of_node)
  20:         return -ENODEV;
  21:  
  22:     if (dev->pm_domain)
  23:         return -EEXIST;
  24:  
  25:     ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
  26:                     "#power-domain-cells", 0, &pd_args);
  27:     if (ret < 0) {
  28:         if (ret != -ENOENT)
  29:             return ret;
  30:  
  31:         /*
  32:          * Try legacy Samsung-specific bindings
  33:          * (for backwards compatibility of DT ABI)
  34:          */
  35:         pd_args.args_count = 0;
  36:         pd_args.np = of_parse_phandle(dev->of_node,
  37:                         "samsung,power-domain", 0);
  38:         if (!pd_args.np)
  39:             return -ENOENT;
  40:     }
  41:  
  42:     pd = of_genpd_get_from_provider(&pd_args);
  43:     if (IS_ERR(pd)) {
  44:         dev_dbg(dev, "%s() failed to find PM domain: %ld\n",
  45:             __func__, PTR_ERR(pd));
  46:         of_node_put(dev->of_node);
  47:         return PTR_ERR(pd);
  48:     }
  49:  
  50:     dev_dbg(dev, "adding to PM domain %s\n", pd->name);
  51:  
  52:     while (1) {
  53:         ret = pm_genpd_add_device(pd, dev);
  54:         if (ret != -EAGAIN)
  55:             break;
  56:         cond_resched();
  57:     }
  58:  
  59:     if (ret < 0) {
  60:         dev_err(dev, "failed to add to PM domain %s: %d",
  61:             pd->name, ret);
  62:         of_node_put(dev->of_node);
  63:         return ret;
  64:     }
  65:  
  66:     dev->pm_domain->detach = genpd_dev_pm_detach;
  67:  
  68:     return 0;
  69: }
  70: EXPORT_SYMBOL_GPL(genpd_dev_pm_attach);

还蛮复杂,我们先不管细节,看一个大概的过程:

a)of_parse_phandle_with_args负责从device_node指针中,解析指定名称的字段,其中”power-domains”是consumer DTS中的关键字,最终会解出一个struct of_phandle_args类型的变量(回忆一下上面的genpd_xlate_t函数指针,第一个参数就是该类型指针)。

b)解析完成后,调用of_genpd_get_from_provider接口,获取power domain指针。

c)最后调用pm_genpd_add_device接口,将该设备添加到该power domain相应的链表中。

of_genpd_get_from_provider负责最终的domain解析,实现如下:

   1: static struct generic_pm_domain *of_genpd_get_from_provider(
   2:                                         struct of_phandle_args *genpdspec)
   3: {
   4:         struct generic_pm_domain *genpd = ERR_PTR(-ENOENT);
   5:         struct of_genpd_provider *provider;
   6:  
   7:         mutex_lock(&of_genpd_mutex);
   8:  
   9:         /* Check if we have such a provider in our array */
  10:         list_for_each_entry(provider, &of_genpd_providers, link) {
  11:                 if (provider->node == genpdspec->np)
  12:                         genpd = provider->xlate(genpdspec, provider->data);
  13:                 if (!IS_ERR(genpd))
  14:                         break;
  15:         }
  16:  
  17:         mutex_unlock(&of_genpd_mutex);
  18:  
  19:         return genpd;
  20: }

找到一个provider,以data指针为参数,调用xlate回调,剩下的事情,就交给provider自己了。

5)power domain的使用

device获得自己的power domain后,可以利用pm_runtime_get_xxx/pm_runtime_put_xxx接口,增加或减少引用计数,runtime pm core会在合适的时机,调用pm domain提供的power on/off提供的接口,power on或者power off设备。

当然,如果不想使用runtime pm接口,pm domain也提供了其它直接调用的形式,不过不建议使用。

具体的on/off流程,会在下一篇文章继续分析,本文就先到这里了。

 

原创文章,转发请注明出处。蜗窝科技www.wowotech.net

标签: Linux PM 电源管理 framework domain

评论:

youmuchenghuai
2016-11-24 11:24
你好,我想实现插入耳机时,平板(android 4.4)linux-3.4 从深度休眠唤醒。插入时CODEC的SWITCH部分会有中断.
1. 在SWITCH驱动里面 调用了     device_set_wakeup_capable(),device_init_wakeup(),在request_irq()加 IRQF_NO_SUSPEND 标志, 然后enable_irq_wake(),跟代码发现 gic_set_wake()里面的相关gic_arch_extn.irq_set_wake为空,---所以好像没效果。
2.  我另外一个功能是通过GPIO唤醒的,这个已经实现(但不是通过1方法,是SDK在arch/arm/mach-xxx/pm里面做了支持), 所以我想到可能是深度休眠时,SOC有些模块可能power off了,导致无法产生中断。 但是我不知到这个SOC 内部的各模块USB、CODEC等的power_domain具体是怎么划分的?  代码里面可以知道么? 要怎么打开SOC内部CODEC模块的电源以实现休眠唤醒?
wowo
2016-11-24 11:45
@youmuchenghuai:1. 你确信你的irqchip是gic_arch_extn不是gic_chip吗?如果是的话,就要自己想办法咯。
2. 看看代码中pm domain有关的实现,应该可以推出来。
twinkle
2016-10-09 16:05
hi,wowo:
   非常感谢分享这么好的文章,一直在跟着你的文章对照linux设备驱动开发进行学习,
   您在这篇文章最后写道:具体的on/off流程,会在下一篇文章继续分析,我没有找到相应的文章,很好的学习资料,期待wowo能够在空闲的时候继续分享!
wowo
2016-10-09 19:46
@twinkle:"会在下一篇文章继续分析"这篇文章确实没有接着写(考虑到PM domian使用还不是很广泛)~~
yui
2019-11-27 09:49
@wowo:runtime PM和pm domain还会再写吗,代码看的有点懵
bypass
2016-08-30 08:52
power domain一般是用于SOC内部的电源分区吗,同样是电源控制,它与regulator的差异点在哪里呢?
wowo
2016-08-30 08:58
@bypass:power domain关注的是“供电的有无”,通常会有几个相互独立的模块共用同一路供电,不一定都是SOC内部。
regulator关注的是电压/电流(电压多一些)的大小。
二者所对应的物理器件不同,因此使用了不同的软件抽象。
wowo
2015-06-24 16:33
@bear20081015
4. 使能suspend下的wakeup事件(只在系统进入suspend的时候使能)。
=============
这个功能放到driver里面做本来就不规范啊,kernel有标准的实现,可以在suspend的时候,自动enable作为wakeup source的设备的中断。你可以参考“http://www.wowotech.net/linux_kenrel/wakeup_events_framework.html”中的讨论。
bear20081015
2015-06-24 18:23
@wowo:多谢。确实是这样的,不应该放在driver里面做。那这样应该是没问题的。

我在想,如果device使用了power domain,就表示即使在系统没有suspend的时候,设备的power也有可能被关掉,这是不是其实就意味着设备PM runtime时的就可能处于最深的power down状态,而不是像单纯的PM runtime那样还可以选择某些较浅的状态?

还有一个问题请教下,如果想实现级联设备的pm runtime的话,需要怎么指定某个device的parent?是在dts中么?还是需要自己实现呢?
wowo
2015-06-24 21:01
@bear20081015:对,pm domain是用来控制power的。runtime pm描述的是级联设备之间的电源状态转换,是相对规则的关系。然而,任意设备之间,都可能共用power domain。这就是区别。
对于级联设备,最好能在DTS中体现,你可以利用simple-bus。
bear20081015
2015-06-25 19:22
@wowo:谢谢wowo。我今天给team里面做的了pm runtime,power domain和系统suspend的training,真复杂啊,说的口干舌燥。

还有个小问题确认下,如果device用异步操作pm_runtime_get的话,当这个函数返回的时候是不保证此时runtime resume函数被调到的。那么driver owner是不是要在之后的硬件操作中先判断下设备是否已经ACTIVE了呢?这样感觉比较麻烦啊。
wowo
2015-06-25 19:38
@bear20081015:不必客气。呵呵,看不明白不如写明白,写明白不如讲明白,你也做了好事一桩啊~~~
对于异步操作,需要思考一点,driver为什么要异步suspend?因为suspend中要等一些东西(例如传输完成等)。这个时候,driver owner本身就有比较复杂的逻辑,维护一个自身的状态,也是有必要的。只要逻辑清晰、合理,是不怕麻烦的。
bear20081015
2015-06-24 03:01
@wowo, 请教下PM runtime, PM domain, system suspend搅在一起的问题:
1.PM domain和设备在system suspend中的callback之间的关系究竟是什么样的?我看到在power domain的相关函数pm_genpd_XXX中(比如pm_genpd_prepare),都会先判断power domain是否处于off的状态,如果是的话就直接返回。那这是不是意味着在系统suspend过程中,属于这个power domain的设备所有的.prepare/.suspend/.suspend_noirq等函数都不会被调到了?如果是的话,这样做合理么?如果保证power domain off时其设备所处的状态就和比如.suspend中设置的状态一样呢?
2.前面和你讨论过,系统suspend的时候为了不让设备的PM runtime和.suspend等函数一起使用,在系统suspend的不同阶段增加了pm_runtime_get_noresume,__pm_runtime_disable等。但是我发现对于使用PM domain的设备来说,在pm_genpd_prepare中如果发现domain是on的状态,就会直接调__pm_runtime_disable来禁止该dev的pm runtime,这是什么原因呢?是系统suspend一开始就让该设备完全由PM domain来控制么? 谢谢!
wowo
2015-06-24 12:19
@bear20081015:问题1:
如果设备从属于某个pm domain,那么设备的runtime pm就会被pm domain接管,在设备的引用计数为0(调用pm_runtime_put系列函数)时,其pm domain的pm_genpd_runtime_suspend函数会被调用。
pm domain在其下面所有的设备的引用计数为0时,power off 该PM domain(有些例外,例如PM_QOS_FLAG_NO_POWER_OFF等等),使domain的状态为GPD_STATE_POWER_OFF。
因此,如果device所在的pm domain都已经OFF了,device driver的suspend就没有意义了(因为设备已经power off了)。这就是pm domain的初衷:
具有pm domain的设备,如果它允许在runtime suspend的时候被power off,是不需要实现suspend/resume等常规机制的,因为pm domain配合runtime pm,会更彻底。
bear20081015
2015-06-24 13:25
@wowo:wowo, 我能不能这样理解:
1. 具有pm domain的设备,它根本就无法实现suspend/resume等函数,因为这些都被它的PM domain接管了。所以原本在suspend/resume中实现的内容,都要放在设备的runtime PM中实现了。
2. 没有pm domain的设备,其suspend/resume函数和PM runtime是没有关系的。比如设备在系统suspend之前已经调用了PM runtime,其suspend函数还是会被调到。
wowo
2015-06-24 14:04
@bear20081015:不完全是这样,虽然pm domain接管了,但是还可以调用到driver的suspend:
pm_genpd_suspend->genpd_suspend_dev
bear20081015
2015-06-24 14:48
@wowo:如果suspend和runtime suspend都实现的话,会不会造成这样的问题:
1. 在系统suspend之前power domain是ON的;
2. 调用driver->suspend函数,在这个函数里会将driver的clk关掉;
3. 在pm_genpd_suspend_noirq中,会调用该driver的runtime suspend,这个函数可能会读写寄存器,但因为在步骤2中clk已经关掉了,会造成问题。

如果存在这个问题的话,我觉得driver的suspend还是不应该实现,只要有runtime suspend就可以了,当然runtime suspend要能完成suspend的所有功能。
wowo
2015-06-24 15:49
@bear20081015:是不是这种情况不太好说,要看看代码。
我一直觉得runtime pm思路很好,但做的太复杂了,又和suspend/resume、pm domain等搅在一起。简单的用一下,还okay。但往仔细里使用,是相当头大的。
bear20081015
2015-06-24 16:05
@wowo:是的。个人觉得这三者搅在一起,有些问题根本就无法解决啊:比如device的suspend函数原本有:
1. 保存软件状态;
2. 关闭clk;
3. 掉电;
4. 使能suspend下的wakeup事件(只在系统进入suspend的时候使能)。

当使用了PM domain后,操作1可以塞到device的pm runtime函数中, 操作2,3可以塞到power domain的runtime中,但是操作4就没地方塞了,就算放在suspend函数中也可能不被调到。这种问题是不是就不能用PM domain了?
wowo
2015-06-24 14:05
@bear20081015:具有pm domain的设备,也可以不用runtime pm,不用pm domain,转而使用常规的suspend、resume。
wowo
2015-06-24 14:10
@bear20081015:问题2:
pm domain的prepare函数被调用时,说明系统进入了常规的suspend、resume过程(参考“http://www.wowotech.net/linux_kenrel/suspend_and_resume.html”),prepare是在为suspend调用做准备,因此顺便把runtime PM禁止掉,处理会比较简单。
bear20081015
2015-06-24 14:55
@wowo:也就是说,
1. 对于只使用了PM RUNTIME的设备,在device_suspend_late中才会将pm runtime disable掉;
2. 而对于使用了PM DOMAIN的设备, 在device_prepare中就会将pm runtime disable掉?
唉,觉得open source这样的设计真是复杂无比,看得头疼。干脆都在系统suspend的prepare阶段把pm runtime都干掉算了....
wowo
2015-06-24 15:44
@bear20081015:是啊,确实比较复杂。可能pm domain涉及到供电,比较谨慎吧。也可能是suspend/resume、runtime pm、pm domain不是一起进化的,可能未来的某一天会简化一些。
qinghu
2015-05-15 16:40
你好,我在看了你的文章后,有个疑问,在PM suspend的过程中,会时不时的调用函数
pm_wakeup_pending,来检查是否有wakeup. 第一,调用这个函数的时间点可以改动吗>还是原来的位置是有特别意义的?第二,freezing  process的过程中,如果有wakeup event.怎么检查唤醒源是谁?那个进程持有的wakeup source 被active 了?有什么调试方法吗?谢谢!
wowo
2015-05-15 17:14
@qinghu:第1个问题:
对suspend而言,pm_wakeup_pending的调用时机也不多啊,就两个地方:
1)process freeze之后。
2)arch_suspend_disable_irqs调用之后。

调用的时间点是否能改动呢?可以很坚决的说,不可以(当然,你在自己的代码中多添加几个检查点是可以的)。原因有二:
1)不要随意修改kernel的原有实现(除非必要),这是我们linux工程师应该坚守的原则。
2)这两个检查点,确实是经过考虑的。

第一个检查点,所有task已经被freeze了,也就是说,这个时候所有的wakeup event都不能被用户空间处理了。所以有必要检查一下是否需要放弃本次suspend动作。其实这个检查可以推后到任何时候(直到第二个检查点),但代价是用户进程的处理延迟。

第二个检查点,中断被关闭了,也就意味着此时不可能再产生wakeup event了。这时检查一次,可以处理第一个检查点到第二个检查点之间的这段时间产生的event。

那第二个检查点之后的事情呢?由具体的suspend指令保证。
wowo
2015-05-15 17:19
@qinghu:第二个问题:
freezing  process的过程中产生了wakeup event.,分两种情况:
1)处理这个event的进程还活着,CPU调度到该进程,该进程根据实际情况,处理event,阻止或者运行系统继续suspend。
2)处理这个event的进程被freeze了,就回到第一个问题中的第一个检查点了。
“怎么检查唤醒源是谁?”,我不太明白是什么意思。谁检查呢?
wowo
2015-05-15 17:27
@qinghu:第三个问题:
进程信息?首要对kernel态的wakeup source来说,没有进程的概念。
只有wakelocks是由进程创建的,从数据结构中看,没有保存进程信息。但这个wakelocks是哪个进程创建的、取什么名字,设计者应该很清楚吧?如果真的需要,就在创建的时候,加入自身的信息吧。
qinghu
2015-05-15 17:41
@wowo:您能这么快回复,而且如此仔细,真的好专业,非常非常感动.
我是遇到个问题,在进行suspend 的过程中.发现进行不下去了.
Freezing user space processes ...
[   54.280000] PM: Wakeup pending, aborting suspend
[   54.290000] last active wakeup source: mmc_delayed_work
应该是在下面的调用过程中,
pm_suspend-->enter_state-->suspend_prepare-->suspend_freeze_processes-->freeze_processes-->try_to_freeze_tasks.你所说的第一个检查点调用pm_wakeup_pending后,发现active wakeup source : mmc_delayed_work,所以睡不下去了.
我怎么找到这个wakelock所属的进程信息呢?是谁拿着锁不让系统睡下去的?
wowo
2015-05-20 15:34
@qinghu:mmc_delayed_work阻止了系统的睡眠。
cainiao
2015-03-22 20:58
好感谢wowo的回复!
cainiao
2015-03-22 18:49
hi,wowo:
  请教一个问题,假如android手机关机了,系统时钟的时间停止不动,也就是开机时间还是之前开机的时间,问题可能会出现在哪里呢?如果手机重启,时间恢复到了1970年,问题可能出现在哪里呢?期待wowo大神出书,持续关注中...
wowo
2015-03-22 20:24
@cainiao:系统启动时,会从RTC中同步系统时间,如果RTC时间非法,系统时间则设为0(可能是1970)。因此,您这第一种情况可能是RTC停止了,没有更新。第二种情况可能是RTC直接返回无效值了。具体可以参考http://www.wowotech.net/timer_subsystem/timekeeping.html,或者让我们华南区时间子系统首席linuxer同学再详细解释一下。

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog