最近工作上接触到了kernel power domain framework,kernel文档对这块的解释不多,网上的参考资料也很少,废话不多说,梳理一下,也让自己加深一下印象。
电源域(power domain)可以理解为多个模块共用一个电源,比如usb、gpio在soc的内部实现上都属于同一个xxx子系统,那么xxx子系统要进入低功耗状态的前提是什么?就是usb和gpio都得进入低功耗;那反过来,如果要使用usb呢?usb要能上电的前提就是xxx子系统得先上电(即xxx子系统的控制寄存器的enable位需要置1)。
那么usb和gpio在软件实现上需要如何进入低功耗呢?相信大家也能猜到,那就是runtime_pm,所以power domain和runtime_pm是存在耦合关系的,本篇文章不会深入分析runtime_pm,想了解的朋友见上一篇。
power domian的使用方法非常的简单,如果不想去深入分析power domain如何工作的,只想使用kernel提供的方法,那么只需在dts中定义一个power domain节点,同时在驱动中注册该power domain即可,是不是很简单?(参考:Documentation\devicetree\bindings\power)
//DTS中定义power domain节点,这里以内核文档提供的例程
parent: power-controller@12340000 {
compatible = "foo,power-controller";
reg = <0x12340000 0x1000>;
#power-domain-cells = <1>; //这里表明parent节点是一个power domain,也就是内核文档所形容的provider,parent管理它下面挂接着的其它模块,为它们提供电源
};
child: power-controller@12341000 {
compatible = "foo,power-controller";
reg = <0x12341000 0x1000>;
power-domains = <&parent 0>; //这里表明child节点是parent下面的一个模块,它使用parent提供的电源
#power-domain-cells = <1>;
};
从上面的dts语法中可以看出,一个系统的电源域从dts中可以很容易就看出来,#power-domain-cells声明该节点是一个power domain,power-domains = <&xxx x>表明该节点属于xxx电源域,那么系统中某个电源域下面有多少个模块就从dts中看有多少个节点引用了该电源域即可。
介绍完了dts,那么驱动中如何注册一个电源域呢?
/**
* pm_genpd_init - Initialize a generic I/O PM domain object.
* @genpd: PM domain object to initialize.
* @gov: PM domain governor to associate with the domain (may be NULL).
* @is_off: Initial value of the domain's power_is_off field.
*
* Returns 0 on successful initialization, else a negative error code.
*/
int pm_genpd_init(struct generic_pm_domain *genpd,
struct dev_power_governor *gov, bool is_off) //初始化一个 generic_pm_domain 实例
/**
* of_genpd_add_provider_onecell() - Register a onecell PM domain provider
* @np: Device node pointer associated with the PM domain provider.
* @data: Pointer to the data associated with the PM domain provider.
*/
//下面两个接口都可以用来向内核注册一个power domian
int of_genpd_add_provider_onecell(struct device_node *np,
struct genpd_onecell_data *data)
int of_genpd_add_provider_simple(struct device_node *np,
struct generic_pm_domain *genpd)
可以看到,驱动想要注册一个power domain只需要调用两个函数即可,甚至不需要实现任何具体的函数。
接下来进入正题,开始分析内核是如何实现power domain的,为何consumer只需引用power domain节点就可以和provider有耦合。
先来看一个非常重要的数据结构,这个数据结构是内核为device电源管理所抽象出来的回调函数,我们关心最后三个即可,即runtime_pm方法,设备的suspend和resume最终都调用该结构体下的某个函数。
struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*freeze_late)(struct device *dev);
int (*thaw_early)(struct device *dev);
int (*poweroff_late)(struct device *dev);
int (*restore_early)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev); //runtime suspend函数
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev); //runtime resume函数
};
前面提到过驱动想要注册一个power domain只需要两个函数即可,那么分析power domain就从这两个函数入手吧。pm_genpd_init 这个函数从注释可以看出它初始化了一个power domain实例,从入口参数结合具体的实现代码,可以很容易得到 struct generic_pm_domain 结构体就对应着一个power domain实例。
struct generic_pm_domain {
struct dev_pm_domain domain; /* PM domain operations */
struct list_head gpd_list_node; /* Node in the global PM domains list */
struct list_head master_links; /* Links with PM domain as a master */
struct list_head slave_links; /* Links with PM domain as a slave */
struct list_head dev_list; /* List of devices */
struct mutex lock;
struct dev_power_governor *gov;
struct work_struct power_off_work;
struct fwnode_handle *provider; /* Identity of the domain provider */
bool has_provider;
const char *name;
atomic_t sd_count; /* Number of subdomains with power "on" */
enum gpd_status status; /* Current state of the domain */
unsigned int device_count; /* Number of devices */
unsigned int suspended_count; /* System suspend device counter */
unsigned int prepared_count; /* Suspend counter of prepared devices */
int (*power_off)(struct generic_pm_domain *domain); //驱动只要实现该函数即可
int (*power_on)(struct generic_pm_domain *domain); //驱动只要实现该函数即可
struct gpd_dev_ops dev_ops;
s64 max_off_time_ns; /* Maximum allowed "suspended" time. */
bool max_off_time_changed;
bool cached_power_down_ok;
int (*attach_dev)(struct generic_pm_domain *domain,
struct device *dev);
void (*detach_dev)(struct generic_pm_domain *domain,
struct device *dev);
unsigned int flags; /* Bit field of configs for genpd */
struct genpd_power_state states[GENPD_MAX_NUM_STATES];
unsigned int state_count; /* number of states */
unsigned int state_idx; /* state that genpd will go to when off */
};
该结构体成员很多,看着很复杂,但是真正需要驱动去赋值的成员只有两个,那就是int (*power_off)(struct generic_pm_domain *domain); 与int (*power_on)(struct generic_pm_domain *domain);,其它的power domain framework已经帮我们做好了,这里我们也可以猜到一个电源域的打开与关闭最终是通过驱动注册的这两个函数操作的,因为该电源域的操作只有编写驱动的工程师才最了解,比如它的寄存器地址、bit位等等。
当驱动得到一个power domain实例后,便可以调用of_genpd_add_provider_simple函数向内核注册一个power domain了,来进入该函数看看,做了哪些事情,看不太懂,但是从入口参数看,传进去了驱动实现的struct generic_pm_domain和power domain的node节点。后续这两个参数应该会有作用。
cousmer的分析很复杂,可能有朋友会疑惑,cousmer只需要在dts中引用所使用的power domain节点即可,啥事都不用做,为何还复杂?哈哈,那是因为kernel在背后帮我们驱动工程师完成了太多事情,所以我们感知不到。
正如上面所述,consumer只需在dts中声明即可和provider耦合,那要分析如何完成耦合的,就需要consumer被加载的时候开始分析,consumer其实就是一个电源域下的device,device的加载就涉及到platform bus了。
drivers/base/platform.c
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
int ret;
ret = of_clk_set_defaults(_dev->of_node, false);
if (ret < 0)
return ret;
ret = dev_pm_domain_attach(_dev, true); //将设备与电源域进行耦合
...
}
int genpd_dev_pm_attach(struct device *dev)
{
struct of_phandle_args pd_args;
struct generic_pm_domain *pd;
unsigned int i;
int ret;
if (!dev->of_node)
return -ENODEV;
if (dev->pm_domain)
return -EEXIST;
ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
"#power-domain-cells", 0, &pd_args); //该函数的作用就是解析device引用的power domain节点的所包含的参数,最终幅值给pd_args结构
if (ret < 0) {
if (ret != -ENOENT)
return ret;
pd_args.args_count = 0;
pd_args.np = of_parse_phandle(dev->of_node,
"samsung,power-domain", 0);
if (!pd_args.np)
return -ENOENT;
}
mutex_lock(&gpd_list_lock);
pd = genpd_get_from_provider(&pd_args); //根据pd_args结构成员获取provider dts的phandle
of_node_put(pd_args.np);
if (IS_ERR(pd)) {
mutex_unlock(&gpd_list_lock);
dev_dbg(dev, "%s() failed to find PM domain: %ld\n",
__func__, PTR_ERR(pd));
return -EPROBE_DEFER;
}
dev_dbg(dev, "adding to PM domain %s\n", pd->name);
for (i = 1; i < GENPD_RETRY_MAX_MS; i <<= 1) {
ret = genpd_add_device(pd, dev, NULL); //给该power domain添加设备信息
if (ret != -EAGAIN)
break;
mdelay(i);
cond_resched();
}
mutex_unlock(&gpd_list_lock);
if (ret < 0) {
dev_err(dev, "failed to add to PM domain %s: %d",
pd->name, ret);
goto out;
}
dev->pm_domain->detach = genpd_dev_pm_detach; //一些初始化过程
dev->pm_domain->sync = genpd_dev_pm_sync; //一些初始化过程
mutex_lock(&pd->lock);
ret = genpd_poweron(pd, 0);
mutex_unlock(&pd->lock);
out:
return ret ? -EPROBE_DEFER : 0;
}
static int genpd_add_device(struct generic_pm_domain *genpd, struct device *dev,
struct gpd_timing_data *td)
{
struct generic_pm_domain_data *gpd_data;
int ret = 0;
dev_dbg(dev, "%s()\n", __func__);
if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev))
return -EINVAL;
gpd_data = genpd_alloc_dev_data(dev, genpd, td);
if (IS_ERR(gpd_data))
return PTR_ERR(gpd_data);
mutex_lock(&genpd->lock);
if (genpd->prepared_count > 0) {
ret = -EAGAIN;
goto out;
}
ret = genpd->attach_dev ? genpd->attach_dev(genpd, dev) : 0;
if (ret)
goto out;
genpd->device_count++;
genpd->max_off_time_changed = true;
list_add_tail(&gpd_data->base.list_node, &genpd->dev_list);
out:
mutex_unlock(&genpd->lock);
if (ret)
genpd_free_dev_data(dev, gpd_data);
else
dev_pm_qos_add_notifier(dev, &gpd_data->nb);
return ret;
}
static int genpd_add_device(struct generic_pm_domain *genpd, struct device *dev,
struct gpd_timing_data *td)
{
struct generic_pm_domain_data *gpd_data;
int ret = 0;
dev_dbg(dev, "%s()\n", __func__);
if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev))
return -EINVAL;
gpd_data = genpd_alloc_dev_data(dev, genpd, td);
if (IS_ERR(gpd_data))
return PTR_ERR(gpd_data);
mutex_lock(&genpd->lock);
if (genpd->prepared_count > 0) {
ret = -EAGAIN;
goto out;
}
ret = genpd->attach_dev ? genpd->attach_dev(genpd, dev) : 0;
if (ret)
goto out;
genpd->device_count++;
genpd->max_off_time_changed = true;
list_add_tail(&gpd_data->base.list_node, &genpd->dev_list);
out:
mutex_unlock(&genpd->lock);
if (ret)
genpd_free_dev_data(dev, gpd_data);
else
dev_pm_qos_add_notifier(dev, &gpd_data->nb);
return ret;
}
static struct generic_pm_domain_data *genpd_alloc_dev_data(struct device *dev,
struct generic_pm_domain *genpd,
struct gpd_timing_data *td)
{
struct generic_pm_domain_data *gpd_data;
int ret;
ret = dev_pm_get_subsys_data(dev);
if (ret)
return ERR_PTR(ret);
gpd_data = kzalloc(sizeof(*gpd_data), GFP_KERNEL);
if (!gpd_data) {
ret = -ENOMEM;
goto err_put;
}
if (td)
gpd_data->td = *td;
gpd_data->base.dev = dev;
gpd_data->td.constraint_changed = true;
gpd_data->td.effective_constraint_ns = -1;
gpd_data->nb.notifier_call = genpd_dev_pm_qos_notifier;
spin_lock_irq(&dev->power.lock);
if (dev->power.subsys_data->domain_data) {
ret = -EINVAL;
goto err_free;
}
dev->power.subsys_data->domain_data = &gpd_data->base;
spin_unlock_irq(&dev->power.lock);
dev_pm_domain_set(dev, &genpd->domain); //重点来啦,设置了struct device的struct dev_pm_domain成员
return gpd_data;
err_free:
spin_unlock_irq(&dev->power.lock);
kfree(gpd_data);
err_put:
dev_pm_put_subsys_data(dev);
return ERR_PTR(ret);
}
贴出来了这么多代码,可见consumer的分析确实蛮复杂,接下来做个总结:
为什么说上面第三点调用的函数很重要呢?文章开头本人介绍了一个很重要的结构体,设备的低功耗操作几乎都是引用驱动注册到该结构体的函数。下面再介绍一个对power domain很重要的结构体,那就是struct dev_pm_domain
struct dev_pm_domain {
struct dev_pm_ops ops; //有没有发现,这个结构体嵌入了设备power manage的结构体
void (*detach)(struct device *dev, bool power_off);
int (*activate)(struct device *dev);
void (*sync)(struct device *dev);
void (*dismiss)(struct device *dev);
};
该结构体最重要的就是嵌入了struct dev_pm_ops结构体,该结构体是设备低功耗的具体操作函数,我们再来看看pm_genpd_init函数里具体的实现。
int pm_genpd_init(struct generic_pm_domain *genpd,
struct dev_power_governor *gov, bool is_off)
{
......
genpd->domain.ops.runtime_suspend = genpd_runtime_suspend;
genpd->domain.ops.runtime_resume = genpd_runtime_resume;
genpd->domain.ops.prepare = pm_genpd_prepare;
genpd->domain.ops.suspend_noirq = pm_genpd_suspend_noirq;
genpd->domain.ops.resume_noirq = pm_genpd_resume_noirq;
genpd->domain.ops.freeze_noirq = pm_genpd_freeze_noirq;
genpd->domain.ops.thaw_noirq = pm_genpd_thaw_noirq;
genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq;
genpd->domain.ops.complete = pm_genpd_complete;
......
}
关注重点,power domain framework在初始化一个struct generic_pm_domain实例的时候,dev_pm_domain下的dev_pm_ops被初始化为power domain特有的函数,即设备属于power domain时,设备在发起suspend和resume流程时,都是调用到power domain的genpd_runtime_suspend和genpd_runtime_resume函数。
这也是很好理解的,因为设备一旦属于一个power domain,设备发起suspend和resume必须要让power domain framework感知到,这样它才能知道每个power domain下的设备当前状态,才能在合适的时机去调用provider的power on和power off函数。但是设备具体的suspend和resume肯定还是驱动自己编写,只有驱动对自己的设备最熟悉,其实可以猜到genpd_runtime_suspend函数只是完成一些power domain framework的一些逻辑,最终还是要调用到设备自己的suspend函数,可以看看代码验证一下猜想。
static int __genpd_runtime_suspend(struct device *dev)
{
int (*cb)(struct device *__dev);
if (dev->type && dev->type->pm)
cb = dev->type->pm->runtime_suspend;
else if (dev->class && dev->class->pm)
cb = dev->class->pm->runtime_suspend;
else if (dev->bus && dev->bus->pm)
cb = dev->bus->pm->runtime_suspend;
else
cb = NULL;
if (!cb && dev->driver && dev->driver->pm)
cb = dev->driver->pm->runtime_suspend;
return cb ? cb(dev) : 0;
}
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
这里验证了本人的猜想,genpd_runtime_suspend函数最终调用了__genpd_runtime_suspend函数,而该函数的作用就是取具体的设备低功耗操作函数,对于platform设备,dev->bus及dev->bus->pm为真,所以最终cb应该是cb = dev->bus->pm->runtime_suspend,
这个时候有些朋友可能会有疑问,设备驱动似乎并没有去初始化dev->bus->pm->runtime_suspend函数呀。是的,驱动没有做,但是内核帮我们做了。
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
static const struct dev_pm_ops platform_dev_pm_ops = {
.runtime_suspend = pm_generic_runtime_suspend,
.runtime_resume = pm_generic_runtime_resume,
USE_PLATFORM_PM_SLEEP_OPS
};
int pm_generic_runtime_suspend(struct device *dev)
{
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
int ret;
ret = pm && pm->runtime_suspend ? pm->runtime_suspend(dev) : 0;
return ret;
}
platform driver在注册的时候,会传入platform_bus_type结构体,该结构体dev_pm_ops成员被赋值为platform_dev_pm_ops结构体,该结构体实现了platform具体的suspend和resume操作,pm_generic_runtime_suspend 该函数呢,最终调用的是dev->driver->pm中的实现方法,也就是驱动实现的runtime suspend和runtime resume。
写到这里,我似乎又多了一点理解,不管是platform bus还是power domain,他们的dev_pm_ops结构体的实现都没有具体的suspend和resume,它们只决定去哪里或者dev_pm_ops,具体的实现还是device driver。只不过power domain相对于platform bus更上层,power domain可以从dev bus、dev type、dev class那里获取dev_pm_ops,而platform则只能从device driver获取具体的实现函数。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/qcot8OHI4-3rJwhccf-cEw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。