关注公众号不迷路:DumpStack
扫码加关注

目录
- 一、热身小菜
- 二、plist - 优先级链表
- 2.1 数据结构
- 2.2 plist_add - 添加节点
- 2.3 plist_del - 删除节点
- 2.4 plist_requeue - 将pilst上的指定node,移动到相同优先级的最末尾
- 2.5 其他接口
- 2.5.1 plist_head_empty - 判断一个plist链表是否为空
- 2.5.2 plist_node_empty - 判断一个node是否已经被挂在plist上了
- 2.5.3 plist_first - 获取plist最前面的node,即最高优先级的node
- 2.5.4 plist_last - 获取plist最后面的node,即最低优先级的node
- 2.5.5 plist_next - 获取指定node在node_list上的下一个plist_node节点
- 2.5.6 plist_prev - 获取指定node在node_list上的前一个plist_node节点
- 2.5.7 plist_for_each - 遍历node_list上的每一个node
- 2.5.8 plist_node_init - 初始化一个plist节点
- 三、QoS机制实现
- 四、Qos在cpuidle子系统中的应用:关注约束最小值
- 4.1 cpu_latency_constraints - 全局变量,用于管理所有的cpu_latency约束
- 4.2 kernel空间的接口
- 4.2.1 cpu_latency_qos_limit - 返回当前系统的cpu延迟的qos约束
- 4.2.2 cpu_latency_qos_request_active - 检查指定的模块的QoS请求是否已经被注册
- 4.2.3 cpu_latency_qos_apply - 因"请求"发生变化,重新计算"约束"的值
- 4.2.4 cpu_latency_qos_add_request - 增加一个新的QoS请求
- 4.2.5 cpu_latency_qos_update_request - 更新QoS请求
- 4.2.6 cpu_latency_qos_remove_request - 移除一个QoS请求
- 4.3 user空间的节点 - /dev/cpu_dma_latency
- 五、QoS在cpufreq子系统中的应用:关注约束最小值和最大值
- 5.1 数据结构
- 5.2 freq_constraints_init - 初始化一个freq约束结构
- 5.3 freq_qos_read_value - 读取freq受约束的值
- 5.4 freq_qos_apply - 更新一个QoS请求
- 5.5 freq_qos_add_request - 增加一个QoS请求
- 5.6 freq_qos_request_active - 判断指定的QoS请求是否生效
- 5.7 freq_qos_update_request - 更新QoS请求
- 5.8 freq_qos_remove_request - 移除一个QoS请求
- 5.9 freq_qos_add_notifier - 增加通知链
- 5.10 freq_qos_remove_notifier - 移除通知链
- 5.11 示例
- 六、QoS在电源管理中的应用:关注某些bit是否被置位
- 七、参考文档
- 关注公众号不迷路:DumpStack
背景:前几天项目上遇到一个问题,在某些场景下cpu进不了深睡眠,导致功耗增加。由上文的分析可知,睡眠的等级是和当时系统可忍受的退出延迟相关,可cpuidle_governor_latency_req获取,这个值是通过QoS子系统计算得到的。通过调试发现在该场景下计算得到的退出延时的值很小,甚至小于idle0的exit-latency的值,导致cpu根本进不了深睡眠。为了搞清楚为啥QoS计算出的退出延迟这么小,决定好好撸一撸这个PM QoS子系统
QoS广泛应用在下面三个领域:
a) 退出延迟类。此时QoS关注最小值,不同的功能模块均可提出自己对退出延迟的"请求",QoS通过决策计算出这些"请求"中最小值,也就是对退出延迟要求最苛刻的值作为约束的值;
b) 调频类。此时QoS即关注最小值也关注最大值。例如一些对性能要求较高的单元可能在某些场景下提出一些最低频的"请求",而另一些对功耗或温控敏感的单元在某些场景下提出对最高频的"请求";QoS整合这些"请求"并计算出一个允许调频的范围,这样在调频时就可以在调频时,在这个范围内选取一个合适的值;
c) 电源管理类。我们可能有这样的需求,某个外设被多个模块使用,当所有模块都不使用这个模块是,我们希望能够将这个外设断电,达到节省功耗的目的。所以我们可以这样做:我们定义一个mask,每个bit对应一个模块,当这个模块正在使用这个外设时我们将对应的bit置一,不使用时清零,这个置一和清零的工作当然是通过增加或修改QoS请求完成的。而电源管理模块提供QoS提供的接口判断是否这些bit全部被清零或置位,以便决定什么时候对外设进行断点;
下面对这三个应用领域分别进行分析,本文分析基于Linux-5.10.61
一、热身小菜
先来热个身,本节摘自窝窝科技,向大牛致敬!
1.1 前言
QoS为Quality of Service(服务质量)的简称,PM QoS表示电源管理相关的服务质量。那到底什么是服务质量呢?
我们知道,Linux PM的主要功能是节省功耗,但在节能的同时会付出一定的性能代价,例如cpu的唤醒延迟(latency)增加、吞吐量(throughput)下降。可以把PM当作一种服务,把它对性能的影响,类比为服务的质量(QoS)。对性能的影响越大,QoS越低,反之越高。
不过,PM QoS framework的存在,并不是为了定义并测量系统的服务质量,而是为了定义一套框架,以满足其他模块(如进程、设备驱动等等)对QoS的期望为终极目标。根据实际的场景,这些期望可描述为:xxx不大于某个值;xxx不小于某个值;等等。
这个终极目标,是基于这样的事实:机器是极端的实用主义者,最理想的状况,是刚刚满足系统各个模块对QoS的期望,因而可以在满足需求的同时,最大化的省电。粗俗一点,就是"我能考60分,为什么要多花一点力气去考61分?"。这样的思路,值得我们深思。
本文将基于PM QoS framework整体的软件架构,介绍它的功能、应用场景、使用方式等。
1.2 软件框架
kernel将"系统各个实体对QoS的期望"抽象为一个一个的constraint(可以理解为约束),围绕这些constraint,可以将系统的实体分为三类:requestor、pm qos framework和requestee。示意图如下:
requestors提出对QoS的constraint。常见的requestor包括应用进程、GPU device、net device、flash device等等,它们基于自身的实际特性,需要系统的QoS满足一定的条件,才能正常工作
pm qos core负责汇整、整理这些constraint,并根据实际情况,计算出它们的极值(最大或者最小)
requestee在需要的时候,从pm qos core处获取constraint的极值,并确保自身的行为,可以满足这些constraints。一般情况下,requestee都是电源管理有关的service,包括cpuidle、runtime pm、pm domain等等
注:requestor和requestee这两个词汇,是蜗蜗自己造出来的,只是为了理解方便。实际上,Linux kernel使用"QoS dependencies"的概念,分别用"Dependents on a QoS value"和"Watchers of QoS value"表述这两个实体,具体可参考kernel/power/qos.c和drivers/base/power/qos.c的文件header。
介绍完requestor和requestee之后,还要再回答一个问题:系统中到底有哪些constraint?这决定了request的对象。截至到目前,PM QoS framework将constraint分为2类,每一类包括若干个constraint,具体如下。
a) 系统级的constraint,包括cpu&dma latency、network latency、network throughput和memory bandwidth 4个constraint。
b) 设备级的constraint,包括从低功耗状态resume的latency、active状态的latency和一些QoS flag(如是否允许power off)。
根据上面2类constraint,kernel提供了2个不同的QoS framework:
a) 一个是系统级别的,用于cpu&dma latency、network latency、network throughput、memory bandwidth等场合,称作PM QoS classes framework。源码位于kernel/power/qos.c
b) 另一个是设备级别的,用于per-device场合,称作per-device PM QoS framework。源码位于drivers/base/power/qos.c
这2个framework有着共同的目标,即:向requestors提供request的add、modify、remove等接口,用于管理QoS requests;将QoS requests分门别类,计算出极值(简称value),向requestee提供request value的查询接口。其软件架构如下:
需要说明的是,PM QoS classes framework会通过misc设备,向用户空间程序提供PM QoS的request、modify、remove功能,以便照顾到它们对PM QoS的需求。
二、plist - 优先级链表
QoS有关注点分下面两种:
最值: 关心最小值或最大值,这类QoS的实现基础是plist,因为plist链表的两端分别对应最大值和最小值
mask: 关心某些bit位是否被置位,这个实现只要靠一个简单的list即可,具体实现后面分析
本章主要对plist的实现原理进行详细的分析
2.1 数据结构
2.1.1 plist_head - 优先级链表头
struct plist_head { struct list_head node_list; }; |
2.1.2 plist_node - 用于挂到优先级链表上的节点
prio_list和node_list链表的示意图如下,prio_list和node_list都是按照优先级数值从小到大的顺序排列(plist认为排在前面的优先级越高)
数据结构定义如下:
struct plist_node { //描述当前节点的优先级 int prio;
//prio_list用于串联不同优先级的节点,按照优先级由小到大的顺序排列, //主要用于在链表元素过多时,做快速的优先级查找 struct list_head prio_list;
//node_list用于串联所有节点,按优先级由小到大的顺序排列, //对于相同优先级的节点,按照先后顺序依次位置 struct list_head node_list; }; |
2.2 plist_add - 添加节点
/** * plist_add - add @node to @head * * @node: &struct plist_node pointer * @head: &struct plist_head pointer */ void plist_add( struct plist_node *node, //要插入的新节点 struct plist_head *head) //要插入到那个链表中 { struct plist_node *first, *iter, *prev = NULL; struct list_head *node_next = &head->node_list;
//1.调试相关 plist_check_head(head);
//2.检查这个node是否已经被挂入node_list和prio_list链表 WARN_ON(!plist_node_empty(node)); WARN_ON(!list_empty(&node->prio_list));
//3.检查链表上是否还没有被插入过任何节点,如果没有任何节点 // 则直接跳转插入,不用执行下面"找到合适的插入位置"的工作 if (plist_head_empty(head)) goto ins_node;
//4.代码走到这里说明plist链表上已经存在节点了,因为plist // 链表上的节点是根据优先级依次排序的,所以在插入链表之前需 // 要根据要插入的node的优先级,找到合适的插入位置
//4.1 先取出node_list中的第一个节点 first = iter = plist_first(head);
//4.2 下面循环从前向后遍历prio_list链表中的所有节点,找到合适的插入位置 do { //4.2.1 因为链表上的节点的顺序是按照优先级从小到大排序的,所以下面比较时 // 只要满足node->prio < iter->prio,就表示找到了合适的位置 if (node->prio < iter->prio) { node_next = &iter->node_list; break; }
prev = iter;
//4.2.2 注意这里遍历的是prio_list链表哦,为啥捏?请结合上面的图再思考一下 iter = list_entry(iter->prio_list.next, struct plist_node, prio_list); } while (iter != first);
//4.3 如果这个node对应的优先级已经有一个"代表"被挂在了prio_list链表上了, // 那么这个node就不需要再往prio_list上挂了,否则,满足下面条件则表示这个 // node将作为"代表"被挂在prio_list链表上 // a) 要插入的node的优先级比prio_list链表中的最小优先级还要小, // 此时该优先级一定还没有"代表"在prio_list上,此时应该将这 // 个nodd插在prio_list的最前面 // b) 或者要插入的node对应的优先级,是第一次出现在proi_list上 if (!prev || prev->prio != node->prio) list_add_tail(&node->prio_list, &iter->prio_list);
ins_node: //5.系统中的任何节点都会被插入node_list链表上,需要注意的是,由node_next // 的赋值条件可知,node_list上的几点有如下特性 // a) 所有相同优先级的node是排在一块的 // b) 不同优先级的node按照优先级的顺序排列 list_add_tail(&node->node_list, node_next);
//6.调试相关 plist_check_head(head); } |
2.3 plist_del - 删除节点
/** * plist_del - Remove a @node from plist. * * @node: &struct plist_node pointer - entry to be removed * @head: &struct plist_head pointer - list head */ void plist_del(struct plist_node *node, struct plist_head *head) { //1.调试相关 plist_check_head(head);
//2.先摘取prio_list // 这里判断的表示:如果有两个相同优先级的node,那么可能存在一个node // 只挂在node_list上,但是他并没有挂在prio_list上。例如上图中的 // node2,5,6,所以如果这个node并没有挂在prio_list上,则不需要执行 // 摘取工作 if (!list_empty(&node->prio_list)) { //2.1 处理上图中node1,4这样的节点,重新选取"代表" // 同一个优先级的node可能存在1个或多个,当存在多个同优先级的 // node时,并且当前要摘除的node正好也被作为"代表"挂在prio_list // 上,如上图node1,4那样,要把这个节点摘除就必须先从相同优先级的 // node中重新选出一个"代表"挂在prio_list上。但是如果像上图 // node3,7,8那样,相同优先级只有一个,也就没有必要重新选取"代表"了
// 下面代码实现中,if判断条件中和head->node_list比较有点奇怪啊 // a) 只有node8才会满足(node->node_list.next == &head->node_list) // 这时候因为node8是他这个优先级中唯一一个node,所以不需要进入 // 下面if分支重新选代表 // b) 而对于node1,3,4,7节点,node->node_list.next一定不等于 // head->node_list,都会进入该if分支,但是在选代表时,巧妙 // 之处就是下面的list_empty判断。 // i) 例如对于node4,选出的next为node5,因为node5没有被挂 // 在prio_list上,list_empty判断为true,则会将node5作 // 为代表插入prio_list上; // ii) 而对于node7,选出的next为node8,但是因为node8已经被 // 插入prio_list上了,所以此时list_empty判断为false,则 // 不会执行插入的动作。 // c) node2,5,6根本不会走到这里 if (node->node_list.next != &head->node_list) { struct plist_node *next;
//2.1.1 从node_list上找到next作为新的"代表" // 获取这个next节点的plist_node结构 next = list_entry(node->node_list.next, struct plist_node, node_list);
/* add the next plist_node into prio_list */ //2.1.2 新"代表"上任 if (list_empty(&next->prio_list)) list_add(&next->prio_list, &node->prio_list); }
//2.2 摘蛋 list_del_init(&node->prio_list); }
//3.摘node_list上的蛋蛋 list_del_init(&node->node_list);
//4.调试相关 plist_check_head(head); } |
2.4 plist_requeue - 将pilst上的指定node,移动到相同优先级的最末尾
将一个已经在plist链表上的node,移动到相同优先级的最末尾,例如对上图node4执行requeue操作,执行完毕后node4排在node6的后面
/** * plist_requeue - Requeue @node at end of same-prio entries. * * This is essentially an optimized plist_del() followed by * plist_add(). It moves an entry already in the plist to * after any other same-priority entries. * * @node: &struct plist_node pointer - entry to be moved * @head: &struct plist_head pointer - list head */ void plist_requeue(struct plist_node *node, struct plist_head *head) { struct plist_node *iter; struct list_head *node_next = &head->node_list;
//1.调试相关 plist_check_head(head);
//2.下面两个检查条件表示: // a) 如果这个plist链表为空,说明链表上没有任何节点,也就不需要requeue // b) 因为requeue操作一定是针对那些已经在plist上的node的操作,如果这 // 个node并不在plist上,也就不可以执行requeue操作 BUG_ON(plist_head_empty(head)); BUG_ON(plist_node_empty(node));
//3.如果要移动的这个node是plist最末尾的node,没法再向后移动了,直接返回 if (node == plist_last(head)) return;
//4.如果这个node已经是排在相同优先级链表最后面了, // 则这个node也就没有移动的空间,直接返回 iter = plist_next(node); if (node->prio != iter->prio) return;
//5.先将这个node从链表上摘除 plist_del(node, head);
//6.遍历找到相同优先级的最尾部,也就是优先级发生变化的位置 // 例如上图中的node4开始遍历,遍历顺序为4,5,6,当iter指向 // node7时,结束遍历,表示已经找到了要插入的位置,node_next指向node7 plist_for_each_continue(iter, head) { if (node->prio != iter->prio) { node_next = &iter->node_list; break; } }
//7.重新插到相同优先级的最末尾,node_next为node7,则插在node7前面 list_add_tail(&node->node_list, node_next);
//8.调试相关 plist_check_head(head); } |
2.5 其他接口
2.5.1 plist_head_empty - 判断一个plist链表是否为空
/** * plist_head_empty - return !0 if a plist_head is empty * @head: &struct plist_head pointer */ static inline int plist_head_empty(const struct plist_head *head) { //因为所有节点都会被插入node_list上,所以直接判断node_list链表即可 return list_empty(&head->node_list); } |
2.5.2 plist_node_empty - 判断一个node是否已经被挂在plist上了
/** * plist_node_empty - return !0 if plist_node is not on a list * @node: &struct plist_node pointer */ static inline int plist_node_empty(const struct plist_node *node) { //一个node一定会被挂在node_list上,但是不一定会被挂在prio_list上 return list_empty(&node->node_list); } |
2.5.3 plist_first - 获取plist最前面的node,即最高优先级的node
/** * plist_first - return the first node (and thus, highest priority) * @head: the &struct plist_head pointer * * Assumes the plist is _not_ empty. */ static inline struct plist_node *plist_first(const struct plist_head *head) { return list_entry(head->node_list.next, struct plist_node, node_list); } |
2.5.4 plist_last - 获取plist最后面的node,即最低优先级的node
/** * plist_last - return the last node (and thus, lowest priority) * @head: the &struct plist_head pointer * * Assumes the plist is _not_ empty. */ static inline struct plist_node *plist_last(const struct plist_head *head) { return list_entry(head->node_list.prev, struct plist_node, node_list); } |
2.5.5 plist_next - 获取指定node在node_list上的下一个plist_node节点
/** * plist_next - get the next entry in list * @pos: the type * to cursor */ #define plist_next(pos) \ list_next_entry(pos, node_list) |
2.5.6 plist_prev - 获取指定node在node_list上的前一个plist_node节点
/** * plist_prev - get the prev entry in list * @pos: the type * to cursor */ #define plist_prev(pos) \ list_prev_entry(pos, node_list) |
2.5.7 plist_for_each - 遍历node_list上的每一个node
/** * plist_for_each - iterate over the plist * @pos: the type * to use as a loop counter * @head: the head for your list */ #define plist_for_each(pos, head) \ list_for_each_entry(pos, &(head)->node_list, node_list) |
2.5.8 plist_node_init - 初始化一个plist节点
/** * plist_node_init - Dynamic struct plist_node initializer * @node: &struct plist_node pointer * @prio: initial node priority */ static inline void plist_node_init( struct plist_node *node, //要初始化的node int prio) //指定node的优先级 { node->prio = prio; INIT_LIST_HEAD(&node->prio_list); INIT_LIST_HEAD(&node->node_list); } |
三、QoS机制实现
3.1 数据结构
3.1.1 pm_qos_constraints - 描述一类约束,例如对cpu_latency的约束
首先要搞清楚"约束"和"请求"的关系,所谓"约束"是相对于整个系统而言的,而"请求"是相对于某一个模块,下面举个例子:
例如当前系统对cpu退出延迟的容忍度可以用一个"约束"来描述,它是相对于整个系统而言的;
而不同的模块对cpu的退出延迟由不同的要求,有的希望长一点,有的希望短一些,即每个模块都有自己的"请求",它是相对于这个模块而言的;
/* * Note: The lockless read path depends on the CPU accessing target_value * or effective_flags atomically. Atomic access is only guaranteed on all CPU * types linux supports for 32 bit quantites */ struct pm_qos_constraints { //用于将自己挂进优先级链表 struct plist_head list;
//该constraint的目标值,即可以满足所有该requestor那个value //通常情况下,根据request的类型,可以是所有request中的最大值或最小值 s32 target_value; /* Do not change to 64 bit */
//该constraint的默认值,通常为0,表示没有限制(或没有要求) //如果在调用pm_qos_update_target更新一个QoS请求时传入的value //为PM_QOS_DEFAULT_VALUE(-1),则使用这个默认值作为新的QoS的值 s32 default_value;
//plist上没有任何node时,也就是说不存在任何约束时pm_qos_get_value返回的值 s32 no_constraint_value;
//当调用pm_qos_get_value时,framework会根据这里的type,决定返回最小值还是最大值 //可为PM_QOS_MAX或PM_QOS_MIN //PM_QOS_MAX表示在所有的request中取最大值,即可满足所有的request,如network_throughput //PM_QOS_MIN表示在所有的request中取最小值,即可满足所有的request,如cpu_dma_latency enum pm_qos_type type;
//用于constraint value改变时通知其它模块 struct blocking_notifier_head *notifiers; }; |
3.1.2 pm_qos_request - 描述一个请求,每个模块都有自己的请求,例如温控、网络等模块
每个模块都对一个子系统都自己的请求,例如模块A在指定的场景下要求cpu_lantency的值小于800us,而模块B在另一个场景下对要求cpu_lantency的值小于500us...那么每个模块对指定场景的需求就可以描述一个请求,对应一个pm_qos_request结构
struct pm_qos_request { struct plist_node node; //plist链表头,用于挂载下面的约束 struct pm_qos_constraints *qos; //约束条件 }; |
3.1.3 pm_qos_type - 在执行qos请求时,用于指示从qos子系统中返回最大值还是最小值
enum pm_qos_type { PM_QOS_UNITIALIZED, PM_QOS_MAX, /* return the largest value */ PM_QOS_MIN, /* return the smallest value */ }; |
3.1.4 pm_qos_req_action - 用于指示增加/更新/移除一个请求
/* Action requested to pm_qos_update_target */ enum pm_qos_req_action { PM_QOS_ADD_REQ, /* Add a new request */ PM_QOS_UPDATE_REQ, /* Update an existing request */ PM_QOS_REMOVE_REQ /* Remove an existing request */ }; |
3.2 pm_qos_get_value - 获取plist上的最大值或最小值
根据type决定返回最大值还是最小值,注意,该函数是一个static类型的,只有QoS子系统自己能使用,外部模块获取约束的值时通过pm_qos_read_value或pm_qos_set_value实现
static int pm_qos_get_value(struct pm_qos_constraints *c) { if (plist_head_empty(&c->list)) return c->no_constraint_value;
//1.根据这个约束是取最大值还是最小值,获取约束的值 switch (c->type) { case PM_QOS_MIN: return plist_first(&c->list)->prio;
case PM_QOS_MAX: return plist_last(&c->list)->prio;
default: WARN(1, "Unknown PM QoS type in %s\n", __func__); return PM_QOS_DEFAULT_VALUE; } } |
3.3 pm_qos_read_value - 获取约束的target_value
从指定约束中获取target_value
/** * pm_qos_read_value - Return the current effective constraint value. * @c: List of PM QoS constraint requests. */ s32 pm_qos_read_value(struct pm_qos_constraints *c) { return READ_ONCE(c->target_value); } |
3.4 pm_qos_set_value - 设置约束的target_value
注意,改函数是static类型,外部模块不能调用。外部模块只能调用pm_qos_read_value获取约束的值,或者通过直接或间接的调用pm_qos_update_target影响约束的值,不能调用pm_qos_set_value直接设置约束的值
static void pm_qos_set_value(struct pm_qos_constraints *c, s32 value) { WRITE_ONCE(c->target_value, value); } |
3.5 pm_qos_update_target - 更新qos约束请求列表
当某个模块的请求发生了变化,就需要间接的调用到该接口,计算这个"约束"的新的值。
需要注意的是,因为很可能这个发生变化了的"请求"并不会影响最终的"约束"的值(例如当这个"请求"是一个中间值时),所以该函数需要一个返回值用于标记新的约束的值是否也发生了变化,调用者需要根据这个值决定是否要执行相应的操作。返回0表示约束的值没有发生变化,返回1表示约束的值已经发生变化
/** * pm_qos_update_target - Update a list of PM QoS constraint requests. * @c: List of PM QoS requests. * @node: Target list entry. * @action: Action to carry out (add, update or remove). * @value: New request value for the target list entry. * * Update the given list of PM QoS constraint requests, @c, by carrying an * @action involving the @node list entry and @value on it. * * The recognized values of @action are PM_QOS_ADD_REQ (store @value in @node * and add it to the list), PM_QOS_UPDATE_REQ (remove @node from the list, store * @value in it and add it to the list again), and PM_QOS_REMOVE_REQ (remove * @node from the list, ignore @value). * * Return: 1 if the aggregate constraint value has changed, 0 otherwise. */ int pm_qos_update_target( struct pm_qos_constraints *c, //要加到哪个qos链表上的约束 struct plist_node *node, //plist链表头 enum pm_qos_req_action action, //要执行的动作,可为add, update, remove int value) //新的约束值 { int prev_value, curr_value, new_value; unsigned long flags;
spin_lock_irqsave(&pm_qos_lock, flags);
//1.从plist上获取约束的最大或最小值 prev_value = pm_qos_get_value(c);
//2.如果传进来的value为PM_QOS_DEFAULT_VALUE(-1),则使用这个 // 约束的默认值default_value作为新值,否则使用刚传进来的value if (value == PM_QOS_DEFAULT_VALUE) new_value = c->default_value; else new_value = value;
//3.根据传入的action完成plist的操作 switch (action) { case PM_QOS_REMOVE_REQ: //3.1 删除约束,就是将这个node从plist链表上删除 plist_del(node, &c->list); break; case PM_QOS_UPDATE_REQ: /* * To change the list, atomically remove, reinit with new value * and add, then see if the aggregate has changed. */ //3.2 更新一个约束的值,要先将其从plist链表上摘下来 // 注意,该case没有break,直接走到下一个case中重新add // 因为这个case没有break,有些编译器可能会编译报错, // 加上这句fallthrough就不报错了 plist_del(node, &c->list); fallthrough; case PM_QOS_ADD_REQ: //3.3 plist节点的优先级就是约束的值,然后直接将其插入plist中 plist_node_init(node, new_value); plist_add(node, &c->list); break; default: /* no action */ ; }
//4.上面对plist链表操作完成后,pilst链表的最大值和最小值很可能 // 已经发生变化,下面重新获取plist的最大或最小值,并更新约束的target_value curr_value = pm_qos_get_value(c); pm_qos_set_value(c, curr_value);
spin_unlock_irqrestore(&pm_qos_lock, flags);
//5.这个trace可以用于定位是谁更改了请求,以及更改后的约束值 trace_pm_qos_update_target(action, prev_value, curr_value);
//6.检查约束的值是否已经发生变化,未发生变化则返回0 if (prev_value == curr_value) return 0;
//7.如果约束的值已经发生变化,则通过notifier通知对应的模块, // 并返回1,以便调用者做出相应的工作 if (c->notifiers) blocking_notifier_call_chain(c->notifiers, curr_value, NULL);
return 1; } |
四、Qos在cpuidle子系统中的应用:关注约束最小值
此处QoS关注的是约束的最小值,在cpu_lantency的应用中,实际就是一堆请求中最苛刻的那个值
cpuidle子系统需要从QoS中获取cpu退出延迟(通过cpu_latency_qos_limit接口),有两种方式:
a) kernel层,可以在driver中直接调用cpu_lantency_qos_xxx接口,实现约束的增加、移除、获取受约束的值等
b) user层的程序可以直接访问/dev/cpu_dma_lantency节点,open该节点的时候增加一个约束,close该节点的时候移除改约束,期间可以通过read、write操作实现对约束的参数进行调整
4.1 cpu_latency_constraints - 全局变量,用于管理所有的cpu_latency约束
static struct pm_qos_constraints cpu_latency_constraints = { //plist链表头,用于连接各个模块请求的约束 .list = PLIST_HEAD_INIT(cpu_latency_constraints.list), .target_value = PM_QOS_CPU_LATENCY_DEFAULT_VALUE, .default_value = PM_QOS_CPU_LATENCY_DEFAULT_VALUE, .no_constraint_value = PM_QOS_CPU_LATENCY_DEFAULT_VALUE, .type = PM_QOS_MIN, //qos请求时返回最小值 }; |
4.2 kernel空间的接口
4.2.1 cpu_latency_qos_limit - 返回当前系统的cpu延迟的qos约束
/** * cpu_latency_qos_limit - Return current system-wide CPU latency QoS limit. */ s32 cpu_latency_qos_limit(void) { return pm_qos_read_value(&cpu_latency_constraints); } |
4.2.2 cpu_latency_qos_request_active - 检查指定的模块的QoS请求是否已经被注册
对于cpu_lantency这类约束中,一个QoS请求被注册的标记就是将req->qos指针指向这个约束,其他类的约束不一定是这样哦
/** * cpu_latency_qos_request_active - Check the given PM QoS request. * @req: PM QoS request to check. * * Return: 'true' if @req has been added to the CPU latency QoS list, 'false' * otherwise. */ bool cpu_latency_qos_request_active(struct pm_qos_request *req) { return req->qos == &cpu_latency_constraints; } |
4.2.3 cpu_latency_qos_apply - 因"请求"发生变化,重新计算"约束"的值
一个新的QoS请求被注册/更新/移出系统时会调用该接口,更新约束,计算出满足所有请求的值
static void cpu_latency_qos_apply( struct pm_qos_request *req, //QoS请求 enum pm_qos_req_action action, //标记是注册还是更新还是移出 s32 value) //这个QoS请求对应的值 { //PM_QOS_DEFAULT_VALUE表示使用当前QoS的值 //1.这个约束的请求发生了变化,在这里重新计算约束的值 // 需要注意的是,因为很可能这个发生变化了的"请求"并不会影响最终 // 的"约束"的值(例如当这个"请求"是一个中间值时),所以下面函 // 数需要一个返回值用于标记新的约束的值是否也发生了变化,返回0表 // 示约束的值没有发生变化,返回1表示约束的值已经发生变化 int ret = pm_qos_update_target(req->qos, &req->node, action, value);
//2.返回1表示约束的最终值确实已经发生变化,下面唤醒所有已经进入 // idle的cpu,目的是让他们根据新的cpu_lantency重新选择idle等级 if (ret > 0) wake_up_all_idle_cpus(); } |
4.2.4 cpu_latency_qos_add_request - 增加一个新的QoS请求
每个模块都有一个QoS请求,各个模块创建好pm_qos_request结构后,调用下面接口将这个QoS请求增加进系统
/** * cpu_latency_qos_add_request - Add new CPU latency QoS request. * @req: Pointer to a preallocated handle. * @value: Requested constraint value. * * Use @value to initialize the request handle pointed to by @req, insert it as * a new entry to the CPU latency QoS list and recompute the effective QoS * constraint for that list. * * Callers need to save the handle for later use in updates and removal of the * QoS request represented by it. */ void cpu_latency_qos_add_request( struct pm_qos_request *req, s32 value) //约束值,即cpu退出延迟的值 { if (!req) return;
//1.检查指定的请求是否已经被注册进系统了 if (cpu_latency_qos_request_active(req)) { WARN(1, KERN_ERR "%s called for already added request\n", __func__); return; }
//2.排查cpu无法进入深睡眠,可以打开这个trace // 但是如果是通过/dev/cpu_dma_latency节点增加的请求, // 传入的value是PM_QOS_DEFAULT_VALUE为-1 trace_pm_qos_add_request(value);
//3.一个cpu_lantency类的QoS请求被注册的标记就是将req->qos指针 // 指向这个约束,其他类的约束不一定是这样哦,cpu_latency_qos_request_active // 就是利用这一点判断一个请求是否已经被注册 req->qos = &cpu_latency_constraints;
//4.应用这个新的请求 cpu_latency_qos_apply(req, PM_QOS_ADD_REQ, value); } |
4.2.5 cpu_latency_qos_update_request - 更新QoS请求
/** * cpu_latency_qos_update_request - Modify existing CPU latency QoS request. * @req : QoS request to update. * @new_value: New requested constraint value. * * Use @new_value to update the QoS request represented by @req in the CPU * latency QoS list along with updating the effective constraint value for that * list. */ void cpu_latency_qos_update_request( struct pm_qos_request *req, //要更新的QoS请求 s32 new_value) //这个QoS请求的新值 { if (!req) return;
//1.如果这个QoS请求压根就没用,也就不需要更新了 if (!cpu_latency_qos_request_active(req)) { WARN(1, KERN_ERR "%s called for unknown object\n", __func__); return; }
//2.这里的trace对问题定位很有帮助 trace_pm_qos_update_request(new_value);
//3.请求的值实际就是plist链表的优先级 if (new_value == req->node.prio) return;
//4.更新新的请求值 cpu_latency_qos_apply(req, PM_QOS_UPDATE_REQ, new_value); } |
4.2.6 cpu_latency_qos_remove_request - 移除一个QoS请求
/** * cpu_latency_qos_remove_request - Remove existing CPU latency QoS request. * @req: QoS request to remove. * * Remove the CPU latency QoS request represented by @req from the CPU latency * QoS list along with updating the effective constraint value for that list. */ void cpu_latency_qos_remove_request(struct pm_qos_request *req) { if (!req) return;
//1.如果这个请求压根就没有被使用,也就不需要移除 if (!cpu_latency_qos_request_active(req)) { WARN(1, KERN_ERR "%s called for unknown object\n", __func__); return; }
//2.trace调试点 trace_pm_qos_remove_request(PM_QOS_DEFAULT_VALUE);
//3.执行删除动作,注意,传入的value为PM_QOS_DEFAULT_VALUE cpu_latency_qos_apply(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
//4.清空pm_qos_request memset(req, 0, sizeof(*req)); } |
4.3 user空间的节点 - /dev/cpu_dma_latency
打开文件,将会使用默认值向PM QoS framework添加一个QoS请求;关闭文件,会移除相应的请求;写入value,更改请求的值;读取文件,将会获取QoS的极值
注意:
a) 只要对cpu_lantency有要求的模块,都可以open该节点,向全局变量cpu_latency_constraints中增加一个约束
b) 这个约束只在节点open期间有效,close后失效
4.3.1 节点注册
static const struct file_operations cpu_latency_qos_fops = { .write = cpu_latency_qos_write, .read = cpu_latency_qos_read, .open = cpu_latency_qos_open, .release = cpu_latency_qos_release, .llseek = noop_llseek, };
static struct miscdevice cpu_latency_qos_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "cpu_dma_latency", .fops = &cpu_latency_qos_fops, };
static int __init cpu_latency_qos_init(void) { int ret;
ret = misc_register(&cpu_latency_qos_miscdev); if (ret < 0) pr_err("%s: %s setup failed\n", __func__, cpu_latency_qos_miscdev.name);
return ret; } late_initcall(cpu_latency_qos_init); |
4.3.2 cpu_latency_qos_open - 在open时创建一个新的QoS请求
任何模块都可以open该节点,在open的时候创建一个新的QoS请求(即pm_qos_request结构),在close的时候销毁。从这里也能看出,pm_qos_request结构是每个模块对应一个。
static int cpu_latency_qos_open(struct inode *inode, struct file *filp) { struct pm_qos_request *req;
//1.在open的时候申请空间 req = kzalloc(sizeof(*req), GFP_KERNEL); if (!req) return -ENOMEM;
//2.将这个QoS请求加入系统 // PM_QOS_DEFAULT_VALUE为-1 cpu_latency_qos_add_request(req, PM_QOS_DEFAULT_VALUE); filp->private_data = req;
return 0; } |
4.3.3 cpu_latency_qos_release - 在close的时候移除该模块的QoS需求
在移除QoS请求时释放pm_qos_request结构
static int cpu_latency_qos_release(struct inode *inode, struct file *filp) { struct pm_qos_request *req = filp->private_data;
filp->private_data = NULL;
//1.先移除这个请求 cpu_latency_qos_remove_request(req);
//2.在close该节点的时候释放pm_qos_request空间 kfree(req);
return 0; } |
4.3.4 cpu_latency_qos_read - 获取cpu_latency约束的值
这个约束的值是各个模块的QoS请求共同作用后的值
static ssize_t cpu_latency_qos_read( struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct pm_qos_request *req = filp->private_data; unsigned long flags; s32 value;
//1.判断这个模块对应的QoS请求是否已经生效 if (!req || !cpu_latency_qos_request_active(req)) return -EINVAL;
//2.直接读取,注意这里面不是读取约束的target_value // 的值,而是重新从plist上读出的实时值 spin_lock_irqsave(&pm_qos_lock, flags); value = pm_qos_get_value(&cpu_latency_constraints); spin_unlock_irqrestore(&pm_qos_lock, flags);
return simple_read_from_buffer(buf, count, f_pos, &value, sizeof(s32)); } |
4.3.5 cpu_latency_qos_write - 设置cpu_latency约束的值
static ssize_t cpu_latency_qos_write( struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { s32 value;
//1.读取用户空间写入的值 if (count == sizeof(s32)) { if (copy_from_user(&value, buf, sizeof(s32))) return -EFAULT; } else { int ret;
ret = kstrtos32_from_user(buf, count, 16, &value); if (ret) return ret; }
//2.设置新的值 cpu_latency_qos_update_request(filp->private_data, value);
return count; } |
五、QoS在cpufreq子系统中的应用:关注约束最小值和最大值
QoS在cpufreq中的应用主要关注最大值和最小值
5.1 数据结构
5.1.1 freq_constraints - 描述一个freq约束
struct freq_constraints { //最低频约束 struct pm_qos_constraints min_freq; struct blocking_notifier_head min_freq_notifiers;
//最高频约束 struct pm_qos_constraints max_freq; struct blocking_notifier_head max_freq_notifiers; }; |
5.1.2 freq_qos_request - 描述一个freq的QoS请求
struct freq_qos_request { enum freq_qos_req_type type; //用于指示获取最高频还是最低频
struct plist_node pnode; struct freq_constraints *qos; //这个请求所属的约束 }; |
5.1.3 freq_qos_req_type - 用于指示QoS请求的类型是影响最高频还是最低频
enum freq_qos_req_type { FREQ_QOS_MIN = 1, FREQ_QOS_MAX, }; |
5.2 freq_constraints_init - 初始化一个freq约束结构
完成一些成员的初始化
/** * freq_constraints_init - Initialize frequency QoS constraints. * @qos: Frequency QoS constraints to initialize. */ void freq_constraints_init(struct freq_constraints *qos) { struct pm_qos_constraints *c;
//QoS对freq的控制分两个维度,分别是最高频和最低频 c = &qos->min_freq; plist_head_init(&c->list); c->target_value = FREQ_QOS_MIN_DEFAULT_VALUE; c->default_value = FREQ_QOS_MIN_DEFAULT_VALUE; c->no_constraint_value = FREQ_QOS_MIN_DEFAULT_VALUE; c->type = PM_QOS_MAX; c->notifiers = &qos->min_freq_notifiers; BLOCKING_INIT_NOTIFIER_HEAD(c->notifiers);
c = &qos->max_freq; plist_head_init(&c->list); c->target_value = FREQ_QOS_MAX_DEFAULT_VALUE; c->default_value = FREQ_QOS_MAX_DEFAULT_VALUE; c->no_constraint_value = FREQ_QOS_MAX_DEFAULT_VALUE; c->type = PM_QOS_MIN; c->notifiers = &qos->max_freq_notifiers; BLOCKING_INIT_NOTIFIER_HEAD(c->notifiers); } |
其中:
#define FREQ_QOS_MIN_DEFAULT_VALUE 0 #define FREQ_QOS_MAX_DEFAULT_VALUE S32_MAX |
5.3 freq_qos_read_value - 读取freq受约束的值
/** * freq_qos_read_value - Get frequency QoS constraint for a given list. * @qos: Constraints to evaluate. * @type: QoS request type. */ s32 freq_qos_read_value( struct freq_constraints *qos, //从那个约束中获取值 enum freq_qos_req_type type) //指示要读取最小值还是最大值 { s32 ret;
//根据type从约束中读取最高频或最低频 switch (type) { case FREQ_QOS_MIN: ret = IS_ERR_OR_NULL(qos) ? FREQ_QOS_MIN_DEFAULT_VALUE : pm_qos_read_value(&qos->min_freq); break; case FREQ_QOS_MAX: ret = IS_ERR_OR_NULL(qos) ? FREQ_QOS_MAX_DEFAULT_VALUE : pm_qos_read_value(&qos->max_freq); break; default: WARN_ON(1); ret = 0; }
return ret; } |
5.4 freq_qos_apply - 更新一个QoS请求
/** * freq_qos_apply - Add/modify/remove frequency QoS request. * @req: Constraint request to apply. * @action: Action to perform (add/update/remove). * @value: Value to assign to the QoS request. * * This is only meant to be called from inside pm_qos, not drivers. */ int freq_qos_apply( struct freq_qos_request *req, //哪个请求 enum pm_qos_req_action action, //增加/移除/更新 s32 value) //新的请求值 { int ret;
//1.根据这个type决定要更新哪个约束值 switch(req->type) { case FREQ_QOS_MIN: ret = pm_qos_update_target(&req->qos->min_freq, &req->pnode, action, value); break; case FREQ_QOS_MAX: ret = pm_qos_update_target(&req->qos->max_freq, &req->pnode, action, value); break; default: ret = -EINVAL; }
return ret; } |
5.5 freq_qos_add_request - 增加一个QoS请求
/** * freq_qos_add_request - Insert new frequency QoS request into a given list. * @qos: Constraints to update. * @req: Preallocated request object. * @type: Request type. * @value: Request value. * * Insert a new entry into the @qos list of requests, recompute the effective * QoS constraint value for that list and initialize the @req object. The * caller needs to save that object for later use in updates and removal. * * Return 1 if the effective constraint value has changed, 0 if the effective * constraint value has not changed, or a negative error code on failures. */ int freq_qos_add_request( struct freq_constraints *qos, //新的请求增加到哪个约束里面 struct freq_qos_request *req, //要增加的请求 enum freq_qos_req_type type, //这个请求影响最大值还是最小值 s32 value) //要增加的请求的值 { int ret;
if (IS_ERR_OR_NULL(qos) || !req) return -EINVAL;
//1.如果这个QoS请求已经被add,不需要重复add if (WARN(freq_qos_request_active(req), "%s() called for active request\n", __func__)) return -EINVAL;
//2.freq_qos_request_active会根据req->qos判断一个请求是否已经被应用 req->qos = qos; req->type = type;
//3.应用这个请求 ret = freq_qos_apply(req, PM_QOS_ADD_REQ, value); if (ret < 0) { req->qos = NULL; req->type = 0; }
return ret; } |
5.6 freq_qos_request_active - 判断指定的QoS请求是否生效
如果一个freq qos请求已经失效,那么在freq_qos_add_request时会将req->qos指向其所属的约束
static inline int freq_qos_request_active(struct freq_qos_request *req) { return !IS_ERR_OR_NULL(req->qos); } |
5.7 freq_qos_update_request - 更新QoS请求
/** * freq_qos_update_request - Modify existing frequency QoS request. * @req: Request to modify. * @new_value: New request value. * * Update an existing frequency QoS request along with the effective constraint * value for the list of requests it belongs to. * * Return 1 if the effective constraint value has changed, 0 if the effective * constraint value has not changed, or a negative error code on failures. */ int freq_qos_update_request( struct freq_qos_request *req, //要更新的请求 s32 new_value) //新的值 { if (!req) return -EINVAL;
//1.如果这个Qos请求没有生效,则没有必要更新 if (WARN(!freq_qos_request_active(req), "%s() called for unknown object\n", __func__)) return -EINVAL;
//2.QoS请求的值实际就是对于plist的优先级 if (req->pnode.prio == new_value) return 0;
//3.更新 return freq_qos_apply(req, PM_QOS_UPDATE_REQ, new_value); } |
5.8 freq_qos_remove_request - 移除一个QoS请求
/** * freq_qos_remove_request - Remove frequency QoS request from its list. * @req: Request to remove. * * Remove the given frequency QoS request from the list of constraints it * belongs to and recompute the effective constraint value for that list. * * Return 1 if the effective constraint value has changed, 0 if the effective * constraint value has not changed, or a negative error code on failures. */ int freq_qos_remove_request(struct freq_qos_request *req) { int ret;
if (!req) return -EINVAL;
//1.如果这个请求没有被add进约束,当然也就不需要移除 if (WARN(!freq_qos_request_active(req), "%s() called for unknown object\n", __func__)) return -EINVAL;
//2.应用 ret = freq_qos_apply(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
//3.注意下面对req->qos的赋值,在freq_qos_request_active判断会用到 req->qos = NULL; req->type = 0;
return ret; } |
5.9 freq_qos_add_notifier - 增加通知链
当约束的最高频或最低频发生变化时,通过该通知链通知
/** * freq_qos_add_notifier - Add frequency QoS change notifier. * @qos: List of requests to add the notifier to. * @type: Request type. * @notifier: Notifier block to add. */ int freq_qos_add_notifier( struct freq_constraints *qos, //哪个约束 enum freq_qos_req_type type, //最高频还是最低频变化时发出通知 struct notifier_block *notifier) //通知链 { int ret;
if (IS_ERR_OR_NULL(qos) || !notifier) return -EINVAL;
switch (type) { case FREQ_QOS_MIN: ret = blocking_notifier_chain_register(qos->min_freq.notifiers, notifier); break; case FREQ_QOS_MAX: ret = blocking_notifier_chain_register(qos->max_freq.notifiers, notifier); break; default: WARN_ON(1); ret = -EINVAL; }
return ret; } |
5.10 freq_qos_remove_notifier - 移除通知链
/** * freq_qos_remove_notifier - Remove frequency QoS change notifier. * @qos: List of requests to remove the notifier from. * @type: Request type. * @notifier: Notifier block to remove. */ int freq_qos_remove_notifier( struct freq_constraints *qos, enum freq_qos_req_type type, struct notifier_block *notifier) { int ret;
if (IS_ERR_OR_NULL(qos) || !notifier) return -EINVAL;
switch (type) { case FREQ_QOS_MIN: ret = blocking_notifier_chain_unregister(qos->min_freq.notifiers, notifier); break; case FREQ_QOS_MAX: ret = blocking_notifier_chain_unregister(qos->max_freq.notifiers, notifier); break; default: WARN_ON(1); ret = -EINVAL; }
return ret; } |
5.11 示例
示例很简单,直接贴代码了
示例位置:U:\linux-5.10.61\drivers\base\power\qos-test.c
5.11.1 freq_qos_test_readd - 修改请求
/* * Test that a freq_qos_request can be added again after removal * * This issue was solved by commit 05ff1ba412fd ("PM: QoS: Invalidate frequency * QoS requests after removal") */ static void freq_qos_test_readd(struct kunit *test) { //1.分别定义一个约束和请求 struct freq_constraints qos; struct freq_qos_request req; int ret;
//2.对约束和请求进行初始化 freq_constraints_init(&qos); memset(&req, 0, sizeof(req));
//3.获取最低频,获取到的默认值为FREQ_QOS_MIN_DEFAULT_VALUE KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), FREQ_QOS_MIN_DEFAULT_VALUE);
/* Add */ //4.增加一个请求,这个请求是控制最低频不能低于1000KHz // 之后再次从QoS中读取最低频的值,读取到的值为1000KHz // 注意:这里的单位是KHz ret = freq_qos_add_request(&qos, &req, FREQ_QOS_MIN, 1000); KUNIT_EXPECT_EQ(test, ret, 1); KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), 1000);
/* Remove */ //5.移除这个请求后再次读取最低频的值,读取到的值为FREQ_QOS_MIN_DEFAULT_VALUE ret = freq_qos_remove_request(&req); KUNIT_EXPECT_EQ(test, ret, 1); KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), FREQ_QOS_MIN_DEFAULT_VALUE);
/* Add again */ //6.再次增加一个请求,约束最低频为2000KHz,并再次获取,读取到的值为2000KHz ret = freq_qos_add_request(&qos, &req, FREQ_QOS_MIN, 2000); KUNIT_EXPECT_EQ(test, ret, 1); KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), 2000); } |
5.11.2 freq_qos_test_min - 最低频控制
/* Basic test for aggregating two "min" requests */ static void freq_qos_test_min(struct kunit *test) { //1.定义约束和请求 struct freq_constraints qos; struct freq_qos_request req1, req2; int ret;
//2.对约束和请求进行初始化 freq_constraints_init(&qos); memset(&req1, 0, sizeof(req1)); memset(&req2, 0, sizeof(req2));
//3.增加两个请求,一个控制最低频为1000KHz,另一个控制最低频为2000KHz ret = freq_qos_add_request(&qos, &req1, FREQ_QOS_MIN, 1000); KUNIT_EXPECT_EQ(test, ret, 1); ret = freq_qos_add_request(&qos, &req2, FREQ_QOS_MIN, 2000); KUNIT_EXPECT_EQ(test, ret, 1);
//4.获取最低频,此时读取到的值应该是2000KHz KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), 2000);
//5.移除2000KHz的最低频的请求,并再次获取最低频,此时读取到的值应该是1000KHz ret = freq_qos_remove_request(&req2); KUNIT_EXPECT_EQ(test, ret, 1); KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), 1000);
//6.移除1000KHz的最低频的请求,再次获取最低频 // 此时读取到的值应该为FREQ_QOS_MIN_DEFAULT_VALUE ret = freq_qos_remove_request(&req1); KUNIT_EXPECT_EQ(test, ret, 1); KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), FREQ_QOS_MIN_DEFAULT_VALUE); } |
5.11.3 freq_qos_test_maxdef - 最高频控制
/* Test that requests for MAX_DEFAULT_VALUE have no effect */ static void freq_qos_test_maxdef(struct kunit *test) { //1.分别定义一个约束和请求 struct freq_constraints qos; struct freq_qos_request req1, req2; int ret;
//2.对约束和请求进行初始化 freq_constraints_init(&qos); memset(&req1, 0, sizeof(req1)); memset(&req2, 0, sizeof(req2));
//3.读取最高频,此时读取到的值应该为FREQ_QOS_MAX_DEFAULT_VALUE KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MAX), FREQ_QOS_MAX_DEFAULT_VALUE);
//4.新增两个请求,这两个请求控制最大值,阈值暂时传入默认值 ret = freq_qos_add_request(&qos, &req1, FREQ_QOS_MAX, FREQ_QOS_MAX_DEFAULT_VALUE); KUNIT_EXPECT_EQ(test, ret, 0); ret = freq_qos_add_request(&qos, &req2, FREQ_QOS_MAX, FREQ_QOS_MAX_DEFAULT_VALUE); KUNIT_EXPECT_EQ(test, ret, 0);
/* Add max 1000 */ //5.更新请求的值,最高频不超过1000KHz // 此时读取到的值应该为1000KHz ret = freq_qos_update_request(&req1, 1000); KUNIT_EXPECT_EQ(test, ret, 1); KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MAX), 1000);
/* Add max 2000, no impact */ //6.更新请求的值,最高频不超过2000Khz // 返回值为0,表示这个请求并没有影响到最终的约束 ret = freq_qos_update_request(&req2, 2000); KUNIT_EXPECT_EQ(test, ret, 0);
//7.两个QoS请求共同作用后的最高频为2000KHz // 此时读取到的值还应该为1000KHz KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MAX), 1000);
/* Remove max 1000, new max 2000 */ //8.移除哪个1000KHz的请求,再次获取最高频为2000KHz ret = freq_qos_remove_request(&req1); KUNIT_EXPECT_EQ(test, ret, 1); KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MAX), 2000); } |
六、QoS在电源管理中的应用:关注某些bit是否被置位
原理:某个模块正在使用该外设,将对应的bit置一,不使用时清零。QoS提供接口管理这个外设电源对应的mask,同时也提供获取这个mask的接口,当所有bit被清零时断电,只要有一个bit不为0,就不能断电。
6.1 数据结构
6.1.1 pm_qos_flags - 约束
QoS管理bit时很简单,只要用list就可以实现,不需要用到plist
struct pm_qos_flags { //所有请求挂在该list下 struct list_head list;
//list上的所有请求共同作用的mask s32 effective_flags; /* Do not change to 64 bit */ }; |
6.1.2 pm_qos_flags_request - 请求
struct pm_qos_flags_request { //用于挂载到约束的链表上 struct list_head node;
//这个请求影响的约束 s32 flags; /* Do not change to 64 bit */ }; |
6.1.3 pm_qos_flags_status - 标记是否所有bit位被置位
enum pm_qos_flags_status { PM_QOS_FLAGS_UNDEFINED = -1, PM_QOS_FLAGS_NONE, //指定的mask中没有任何bit被置位 PM_QOS_FLAGS_SOME, //指定的mask中只有某些bit被置位 PM_QOS_FLAGS_ALL, //指定的mask中所有bit都被置位 }; |
6.2 pm_qos_flags_remove_req - 移除一个QoS请求,重新计算mask
/** * pm_qos_flags_remove_req - Remove device PM QoS flags request. * @pqf: Device PM QoS flags set to remove the request from. * @req: Request to remove from the set. */ static void pm_qos_flags_remove_req( struct pm_qos_flags *pqf, //约束 struct pm_qos_flags_request *req) //要移除的请求 { s32 val = 0;
//1.从链表摘除 list_del(&req->node);
//2.遍历链表上的每一个pm_qos_flags_request结构,重新计算flags list_for_each_entry(req, &pqf->list, node) val |= req->flags;
//3.重新设置共同作用的flags pqf->effective_flags = val; } |
6.3 pm_qos_update_flags - 更新QoS请求,重新计算mask
/** * pm_qos_update_flags - Update a set of PM QoS flags. * @pqf: Set of PM QoS flags to update. * @req: Request to add to the set, to modify, or to remove from the set. * @action: Action to take on the set. * @val: Value of the request to add or modify. * * Return: 1 if the aggregate constraint value has changed, 0 otherwise. */ bool pm_qos_update_flags( struct pm_qos_flags *pqf, //要更新哪个约束下的flags struct pm_qos_flags_request *req, //要更新的请求 enum pm_qos_req_action action, //指示对请求的操作 s32 val) //请求的值,是一个mask { unsigned long irqflags; s32 prev_value, curr_value;
spin_lock_irqsave(&pm_qos_lock, irqflags);
//1.记录更新前约束的值 // 如果约束链表上没有任何约束,返回的值为0 prev_value = list_empty(&pqf->list) ? 0 : pqf->effective_flags;
//2.执行指定的操作,移除/更新/新增一个请求 // 并重新计算约束的值effective_flags switch (action) { case PM_QOS_REMOVE_REQ: //2.1 移除一个请求时,会重新计算effective_flags pm_qos_flags_remove_req(pqf, req); break; case PM_QOS_UPDATE_REQ: //2.2 更新一个请求 pm_qos_flags_remove_req(pqf, req); fallthrough; case PM_QOS_ADD_REQ: //2.3 增加一个flags请求时,直接或上对应的bit位 req->flags = val; INIT_LIST_HEAD(&req->node); list_add_tail(&req->node, &pqf->list); pqf->effective_flags |= val; break; default: /* no action */ ; }
//3.获取更新后的mask curr_value = list_empty(&pqf->list) ? 0 : pqf->effective_flags;
spin_unlock_irqrestore(&pm_qos_lock, irqflags);
//4.打印trace trace_pm_qos_update_flags(action, prev_value, curr_value);
//5.返回这个请求被增加/更新/移除后,是否影响最终约束的mask return prev_value != curr_value; } |
6.4 dev_pm_qos_flags - 判断指定的mask中的bit位是否全部被设置
在电源管理时应用
/** * dev_pm_qos_flags - Check PM QoS flags for a given device (locked). * @dev: Device to check the PM QoS flags for. * @mask: Flags to check against. */ enum pm_qos_flags_status dev_pm_qos_flags(struct device *dev, s32 mask) { unsigned long irqflags; enum pm_qos_flags_status ret;
spin_lock_irqsave(&dev->power.lock, irqflags); ret = __dev_pm_qos_flags(dev, mask); spin_unlock_irqrestore(&dev->power.lock, irqflags);
return ret; } |
6.4.1 __dev_pm_qos_flags
该函数会返回下面值:
PM_QOS_FLAGS_NONE : 指定的mask中没有任何bit被置位
PM_QOS_FLAGS_SOME : 指定的mask中只有某些bit被置位
PM_QOS_FLAGS_ALL : 指定的mask中所有bit都被置位
/** * __dev_pm_qos_flags - Check PM QoS flags for a given device. * @dev: Device to check the PM QoS flags for. * @mask: Flags to check against. * * This routine must be called with dev->power.lock held. */ enum pm_qos_flags_status __dev_pm_qos_flags(struct device *dev, s32 mask) { struct dev_pm_qos *qos = dev->power.qos; struct pm_qos_flags *pqf; s32 val;
lockdep_assert_held(&dev->power.lock);
//1.不存在任何约束 if (IS_ERR_OR_NULL(qos)) return PM_QOS_FLAGS_UNDEFINED;
//2.约束中没有任何请求 pqf = &qos->flags; if (list_empty(&pqf->list)) return PM_QOS_FLAGS_UNDEFINED;
//3.判断是某些bit位还是所有bit位 val = pqf->effective_flags & mask; if (val) return (val == mask) ? PM_QOS_FLAGS_ALL : PM_QOS_FLAGS_SOME;
//4.mask中指定的bit位没有任何bit位被置位 return PM_QOS_FLAGS_NONE; } |
6.5 示例
6.5.1 在usb中的应用
下面以usb为例,没人使用usb模块时,关闭usb的电源
static int usb_port_runtime_suspend(struct device *dev) { struct usb_port *port_dev = to_usb_port(dev); struct usb_device *hdev = to_usb_device(dev->parent->parent); struct usb_interface *intf = to_usb_interface(dev->parent); struct usb_hub *hub = usb_hub_to_struct_hub(hdev); struct usb_port *peer = port_dev->peer; int port1 = port_dev->portnum; int retval;
if (!hub) return -EINVAL; if (hub->in_reset) return -EBUSY;
//1.如果所有的bit被置位,则表示有人正在使用usb,此时还不能断点 if (dev_pm_qos_flags(&port_dev->dev, PM_QOS_FLAG_NO_POWER_OFF) == PM_QOS_FLAGS_ALL) return -EAGAIN;
//2.如果没人使用这个模块,则走下面逻辑断电 if (usb_port_block_power_off) return -EBUSY;
retval = usb_autopm_get_interface(intf); if (retval < 0) return retval;
retval = usb_hub_set_port_power(hdev, hub, port1, false); usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); if (!port_dev->is_superspeed) usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); usb_autopm_put_interface(intf);
/* * Our peer usb3 port may now be able to suspend, so * asynchronously queue a suspend request to observe that this * usb2 port is now off. */ if (!port_dev->is_superspeed && peer) pm_runtime_put(&peer->dev);
return retval; } |
6.5.2 在acpi中的应用
/** * acpi_pm_device_sleep_state - Get preferred power state of ACPI device. * @dev: Device whose preferred target power state to return. * @d_min_p: Location to store the upper limit of the allowed states range. * @d_max_in: Deepest low-power state to take into consideration. * Return value: Preferred power state of the device on success, -ENODEV * if there's no 'struct acpi_device' for @dev, -EINVAL if @d_max_in is * incorrect, or -ENODATA on ACPI method failure. * * The caller must ensure that @dev is valid before using this function. */ int acpi_pm_device_sleep_state(struct device *dev, int *d_min_p, int d_max_in) { struct acpi_device *adev; int ret, d_min, d_max;
if (d_max_in < ACPI_STATE_D0 || d_max_in > ACPI_STATE_D3_COLD) return -EINVAL;
//1.当要发现进入深睡眠时,需要执行下面的逻辑进行判断,判断是否已经没人使用这个模块了 // 如果还有人使用这个模块,则不能完全断点,保持D2这个等级 // PM_QOS_FLAG_NO_POWER_OFF值为1,表示有人正在使用这个模块,不能断电(NO_POWER_OFF) // 功耗顺序:D0>D1>D2>D3,简单理解,D0全部供电,D3完全断电 if (d_max_in > ACPI_STATE_D2) { enum pm_qos_flags_status stat;
stat = dev_pm_qos_flags(dev, PM_QOS_FLAG_NO_POWER_OFF); if (stat == PM_QOS_FLAGS_ALL) d_max_in = ACPI_STATE_D2; }
adev = ACPI_COMPANION(dev); if (!adev) { dev_dbg(dev, "ACPI companion missing in %s!\n", __func__); return -ENODEV; }
ret = acpi_dev_pm_get_state(dev, adev, acpi_target_system_state(), &d_min, &d_max); if (ret) return ret;
if (d_max_in < d_min) return -EINVAL;
if (d_max > d_max_in) { for (d_max = d_max_in; d_max > d_min; d_max--) { if (adev->power.states[d_max].flags.valid) break; } }
if (d_min_p) *d_min_p = d_min;
return d_max; } |
6.5.3 用户空间控制PM_QOS_FLAG_NO_POWER_OFF
可以通过/sys/devices/.../power/pm_qos_no_power_off节点来控制,简单了解一下
文件位置:W:\opensource\linux-5.10.61\drivers\base\power\sysfs.c
static ssize_t pm_qos_no_power_off_show(struct device *dev, struct device_attribute *attr, char *buf) { //返回值只能是1或0,返回1表示当前不能断电,0表示可以断电 return sysfs_emit(buf, "%d\n", !!(dev_pm_qos_requested_flags(dev) & PM_QOS_FLAG_NO_POWER_OFF)); }
static ssize_t pm_qos_no_power_off_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { int ret;
if (kstrtoint(buf, 0, &ret)) return -EINVAL;
if (ret != 0 && ret != 1) return -EINVAL;
ret = dev_pm_qos_update_flags(dev, PM_QOS_FLAG_NO_POWER_OFF, ret); return ret < 0 ? ret : n; }
static DEVICE_ATTR_RW(pm_qos_no_power_off); |
6.5.3.1 dev_pm_qos_requested_flags
static inline s32 dev_pm_qos_requested_flags(struct device *dev) { return dev->power.qos->flags_req->data.flr.flags; } |
6.5.3.2 dev_pm_qos_update_flags
/** * dev_pm_qos_update_flags - Update PM QoS flags request owned by user space. * @dev: Device to update the PM QoS flags request for. * @mask: Flags to set/clear. * @set: Whether to set or clear the flags (true means set). */ int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set) { s32 value; int ret;
pm_runtime_get_sync(dev); mutex_lock(&dev_pm_qos_mtx);
if (IS_ERR_OR_NULL(dev->power.qos) || !dev->power.qos->flags_req) { ret = -EINVAL; goto out; }
//不管用户空间写啥,只影响PM_QOS_FLAG_NO_POWER_OFF位 value = dev_pm_qos_requested_flags(dev); if (set) value |= mask; else value &= ~mask;
ret = __dev_pm_qos_update_request(dev->power.qos->flags_req, value);
out: mutex_unlock(&dev_pm_qos_mtx); pm_runtime_put(dev); return ret; } |
七、参考文档
Linux-5.10.61/Documentation/power/pm_qos_interface.rst
http://www.wowotech.net/pm_subsystem/pm_qos_overview.html
http://www.wowotech.net/pm_subsystem/pm_qos_class.html
http://www.wowotech.net/pm_subsystem/per_device_pm_qos.html
https://blog.csdn.net/feelabclihu/article/details/116810959
文章评论
test
哇 写的真好啊!
@Tomorrow 哈哈~
你好,我是蜗窝科技(wowotech)的博主linuxer,很高兴认识你,我感觉我们对技术的态度是相同的。我目前在oppo工作,有没有兴趣来oppo一起搞内核。
@linuxer Hi linuxer, 收到您的回复我感到非常荣幸,我一直在关注您的蜗窝科技论坛,并从中学到了许多知识,您是我入门Linux世界的启蒙老师,能够得到您的认可我感到非常开心和荣幸。虽然我最近没有换工作的打算,但希望将来有机会能够与您共事,非常感谢您的邀请!:)