###1. linux PM QOS

PM QOS表示linux 的电源管理服务质量

linux使用 suspned/resume 机制在系统不使用的时候, 整体进入休眠状态,以节省电源,后面又引入更细粒度的runtime PM, 在设备不使用时, 单独suspend一个设备

不管是generic PM还是runtime PM, 都是在设备不被使用的情况下, suspend设备以达到节省电源的目的,我们知道,系统能够根据负载来调节CPU的频率,以取得功耗和性能的平衡,很多设备在运行时, 也能够在功耗和性能之间作出倾斜, 例如, 以手机上的sensor为例,加快采样频率能够使得更加灵敏, 但同时耗电量也会增加, 降低采样频率则正好相反,在不同的场景下(例如正常使用和游戏时), 对精度的要求不同, 应该根据需要, 将设备调节到刚好够用,以达到最大程度的省电

linux PM QOS正是为了定义了这样一套框架, 满足系统中各个实体(例如用户进程, 设备驱动)对Qos的期望

将Qos量化,可能会觉得难以理解

###2. PM Qos简述

在PM Qos中, 有两种实体:

  • Dependents on a QoS value

发起Qos alue请求, 例如应用进程、GPU device、net device、flash device等等,它们基于自身的实际特性,需要系统的QoS满足一定的条件,才能正常工作

  • Watchers of QoS value :

监听Qos value的改变, 并保证自身满足请求, 该类实体多为 电源管理有关的service,包括cpuidle、runtime pm、pm domain等等

PM Qos core提供API(add, update, remove)给“Dependents on a QoS value”类实体来请求Qos Value, 并进行汇总,计算出他们的最大或者最小值, 而“Watchers of QoS value”类实体在需要时, 通过PM Qos core提供的API查询所需的Qos value的极值, 并并确保自身的行为,可以满足这些极值的要求

PM Qos value分为三种:

  1. latency : 延延迟,单位: usec
  2. timeout : 超时, 当前未使用
  3. throughput : 吞吐量, 单位: kbs(kByte/s)

PM Qos分为两类:

  • 系统级别的Qos

目前有四种: cpu&dma latency, network latency, network throughput和和memory bandwidth

  • 单个设备的Qos

包括从低功耗状态resume的latency、active状态的latency和一些QoS flag(如是否允许power off)

PM Qos使用pm_qos_constraints来描述某一个具体的Qos项, 无论是系统级的Qos还是单个设备的Qos, 都是对它的封装:

struct pm_qos_constraints {
	struct plist_head list;
	s32 target_value;	/* Do not change to 64 bit */
	s32 target_per_cpu[NR_CPUS];
	s32 default_value;
	enum pm_qos_type type;
	struct blocking_notifier_head *notifiers;
};

###3. PM Qos class(系统级的PM Qos)

系统级别的PM Qos(PM Qos classes)实现代码位于kernel/power/qos.c, 提供

####3.1 内核空间API

供Dependents on a QoS value类实体(例如设备驱动)发起Qos请求:

void pm_qos_add_request(struct pm_qos_request *req, int pm_qos_class,s32 value);
void pm_qos_update_request(struct pm_qos_request *req, s32 new_value);
void pm_qos_update_request_timeout(struct pm_qos_request *req, s32 new_value, unsigned long timeout_us);
void pm_qos_remove_request(struct pm_qos_request *req);
int pm_qos_request_active(struct pm_qos_request *req);

使用pm_qos_add_request()来添加一个Qos请求, pm_qos_class为以下4种的其中一种:

enum {
	PM_QOS_RESERVED = 0,
	PM_QOS_CPU_DMA_LATENCY,
	PM_QOS_NETWORK_LATENCY,
	PM_QOS_NETWORK_THROUGHPUT,
	PM_QOS_MEMORY_BANDWIDTH
    
	/* insert new class ID */
	PM_QOS_NUM_CLASSES,
};

以PM_QOS_CPU_DMA_LATENCY为例, 它代表当产生一个事件之后(如一个中断),CPU或DMA的响应延迟, 若延迟太大会导致接收buf不能及时清空而丢失数据, 而使用pm_qos_add_request的逻辑可以总结为:我要求CPU&DMA的latency不大于‘value’个us

pm_qos_request结构体为一个PM Qos请求的句柄, 调用者无需关心其内部具体内容, 后续的update, remove操作需要传递该指针

  • pm_qos_update_request() / pm_qos_update_request_timeout() : 用于更新QoS请求, 指定timeout时, 会在到期后将Qos设置为一个默认值(一般为无效值,代表不需要Qos请求)
  • pm_qos_remove_request() : 如果对该pm qos class不再有要求,用于将请求移除
  • pm_qos_request_active() : 获取某一个QoS请求的active状态

供Watchers of QoS value类实体获取Qos变化:

int pm_qos_request(int pm_qos_class);
int pm_qos_add_notifier(int pm_qos_class, struct notifier_block *notifier);
int pm_qos_remove_notifier(int pm_qos_class, struct notifier_block *notifier); 

每当有新的QoS请求时,framework都会根据该QoS class的含义,计算出满足所有请求的一个极值(如最大值、最小值等等)。该值可以通过pm_qos_request()接口获得. 例如cpuidle framework在选择C state时,会通过该接口获得系统对CPU&DMA latency的需求,并保证从C state返回时的延迟小于该value

另外,如果某个实体在意某一个class的QoS value变化,可以通过pm_qos_add_notifier()接口添加一个notifier,这样当value变化时,framework便会通过notifier的回调函数,通知该实体, 而pm_qos_remove_notifier()用于删除notifier

提供给pre-device PM Qos的接口:

int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node,
                         enum pm_qos_req_action action, int value);
bool pm_qos_update_flags(struct pm_qos_flags *pqf,
                         struct pm_qos_flags_request *req,
                         enum pm_qos_req_action action, s32 val);
s32 pm_qos_read_value(struct pm_qos_constraints *c);

这些接口提供给pre-device PM Qos的实现来使用

####3.2 用户空间接口

系统级的PM Qos建立了4个misc device :

  1. /dev/cpu_dma_latency
  2. /dev/network_latency
  3. /dev/network_throughput
  4. /dev/memory_bandwidth

这几个文件的不同操作也对应着PM Qos的操作:

  • open : 使用默认值向PM QoS framework添加一个QoS请求
  • close : 移除相应的请求
  • write : 使用写入值来更新PM Qos请求值
  • read : 获得对应的Qos极值

###4. 系统级Qos的实现

前面提到过, PM Qos使用pm_qos_constraints来描述某一个具体的Qos项, 无论是系统级的Qos还是单个设备的Qos, 都是对它的封装

struct pm_qos_constraints {
	struct plist_head list;
	s32 target_value;	/* Do not change to 64 bit */
	s32 target_per_cpu[NR_CPUS];
	s32 default_value;
	enum pm_qos_type type;
	struct blocking_notifier_head *notifiers;
};
  • list : 链表头, 保存该类Qos的所有请求
  • target_value : 该constraint的目标值,即可以满足所有该class的所有请求值的汇总,通常情况下,根据request的类型(enum pm_qos_type : 可以是所有request中的最大值,所有request中的最小值,或者所有request的和
  • default_value : 该constraint的默认值,通常为0,表示没有限制
  • type : 该constraint的类型, 有PM_QOS_MAX(所有请求值中取最大值), PM_QOS_MIN(所有请求值中取最小值), PM_QOS_SUM(所有请求值中取最小值), 汇总某一class的所有请求值时, 会根据type来更新target_value
  • notifiers: 用于constraint value改变时通知其它driver

系统级的四个Qos分别使用4个pm_qos_constraints结构体实例来表示:

struct pm_qos_object {
   		struct pm_qos_constraints *constraints;
   		struct miscdevice pm_qos_power_miscdev;
   		char *name;
   	};
   
static struct pm_qos_object *pm_qos_array[] = {
  		&null_pm_qos,
  		&cpu_dma_pm_qos,
  		&network_lat_pm_qos,
  		&network_throughput_pm_qos,
  		&memory_bandwidth_pm_qos,
  	};

根据pm_qos_array数组, 就能找到4个系统级PM Qos的对应的pm_qos_constraints结构体

系统级的PM Qos使用struct pm_qos_request作为请求句柄:

struct pm_qos_request {
   		struct plist_node node;
   		int pm_qos_class;
   		struct delayed_work work; /* for pm_qos_update_request_timeout */
};
  • node : 用于将自己放在对应的PM Qos class的链表里,每一个node都有一个优先级(其实是请求的value), 该双向链表按优先级降序排列
  • pm_qos_class : 该request对应的qos class,可以为PM_QOS_CPU_DMA_LATENCY、PM_QOS_NETWORK_LATENCY、PM_QOS_NETWORK_THROUGHPUT、PM_QOS_MEMORY_BANDWIDTH中的一种
  • 一个delay work,用于实现pm_qos_update_request_timeout(到期后自动remove请求)接口

在pm_qos_add_request中, qos array中取出取出对应的constraint指针, 使用该指针和request的value为参数,调用pm_qos_update_target接口

pm_qos_update_target将该请求保存到constraint.list的链表中, 并且更新该class的限值, 若和之前的值不同, 则调用该class的内核通知链(每一种系统级的Qos class都有一个内核通知链)来通知Qos限值改变

在PM Qos class初始化时, 还会为每一种PM Qos class注册一个misc device, 给用户空间提供PM Qos的接口

PM Qos class(系统级的PM Qos)的代码逻辑较为简单,不再详述

###5. pre-device PM Qos(设备级的PM Qos)

pre-device PM Qos是针对指定设备的Qos, 主要关注以下几点:

  • resume_latency

在Runtime PM的框架下,当device的引用计数减为0的时候,RPM会suspend该device。不过,device进入suspend状态以及从suspend状态resume是需要消耗时间的(相关信息保存在pm domain中),而系统其它实体(如用户空间程序)可能对该设备的响应时间有要求,这就是一种形式的QoS request,称作resume_latency
per-device PM QoS framework会提供相应的接口,收集指定设备的resume_latency request,并提供给Runtime PM,它在suspend设备时,会考虑这种需求,并决定是否suspend设备

  • latency_tolerance

一些复杂的设备,在运行状态(active)时,为了节省功耗,也有可能自行进入某些省电状态,相应的,设备的响应速度可能降低。如果该设备足够智能,可能会提供一个回调函数(.set_latency_tolerance,位于dev_pm_info结构中),以便设置最大的延迟容忍时间。这称作latency_tolerance
对per-device PM QoS来说,需要提供一种机制,收集所有的、针对某个设备的latency_tolerance需求,并汇整出可以满足所有需求的latency_tolerance,通过设备的回调函数告知设备

  • no power off/remote wakeup flag

在Runtime PM的框架下,设备suspend之后,还可以进一步通过pm domain关闭该设备的供电,以节省功耗。但关闭供电时,除了要考虑对设备resume_latency的需求之外,还要考虑该设备是否允许关闭供电,以及该设备是否需要作为一个唤醒源(remote wakeup)这是另一种形式的QoS request,称作per-device PM QoS flag,表示系统其它实体对该设备的一些特定行为的需求。当前的flag有两种: PM_QOS_FLAG_NO_POWER_OFF,表示不允许设备断电 PM_QOS_FLAG_REMOTE_WAKEUP,表示设备应具备唤醒功能 这两个flag可以通过或操作,同时生效

同PM Qos class一样pre-device PM Qos也需要提供发起Qos请求的接口以及查询/监听Qos值改变的接口

###6. pre-device PM Qos实现

device.power.qos成员是一个dev_pm_qos结构体:

struct dev_pm_qos {
	struct pm_qos_constraints resume_latency;
    struct pm_qos_constraints latency_tolerance;
    struct pm_qos_flags flags;
    struct dev_pm_qos_request *resume_latency_req;
    struct dev_pm_qos_request *latency_tolerance_req;
    struct dev_pm_qos_request *flags_req;
};
  • resume_latency : 为第一种QoS request,表示其它实体对该设备从suspend状态返回的延迟的要求。struct pm_qos_constraints为pm qos要求的具体抽象
  • latency_tolerance : 为第二种QoS request
  • flags : 为第三种QoS request,主要包括一个链表头,用于保存具体的flag要求,以及汇整后的、当前所有有效的flags
  • 最后三个指针指向上述三种Qos request的句柄, 这些句柄会形成一个链表

dev_pm_qos_request结构体是pre-device PM Qos中Qos请求的句柄

	struct dev_pm_qos_request {
		enum dev_pm_qos_req_type type;
   			union {
   				struct plist_node pnode;
   				struct pm_qos_flags_request flr;
   			} data;
   			struct device *dev;
   		};
  • type : request的类型,当前共三类,包括在前面描述的三种request(DEV_PM_QOS_RESUME_LATENCY、DEV_PM_QOS_LATENCY_TOLERANCE、DEV_PM_QOS_FLAGS)
  • data : 对于前面所说的resume_latency和latency_tolerance请求, 为一个plist的node 类型, 对于flag的请求,为pm_qos_flags_request, 其包含一个数值和一个用于被添加到链表的list_head 节点
  • dev : 指向对应的device

同PM Qos class一样pre-device PM Qos的实现也比较简单, 不再详述

###6.1 API

供其它driver 发起Qos请求

int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
                       enum dev_pm_qos_req_type type, s32 value);
int dev_pm_qos_update_request(struct dev_pm_qos_request *req, s32 new_value);
int dev_pm_qos_remove_request(struct dev_pm_qos_request *req);

供其它driver获取pre-device PM Qos需求的API

enum pm_qos_flags_status dev_pm_qos_flags(struct device *dev, s32 mask);
s32 dev_pm_qos_read_value(struct device *dev);

int dev_pm_qos_add_notifier(struct device *dev,
                        struct notifier_block *notifier);
int dev_pm_qos_remove_notifier(struct device *dev,
                           struct notifier_block *notifier);
int dev_pm_qos_add_global_notifier(struct notifier_block *notifier);
int dev_pm_qos_remove_global_notifier(struct notifier_block *notifier); 
  • dev_pm_qos_flags : 专用于获取device的DEV_PM_QOS_FLAGS类Qos的值
  • dev_pm_qos_add_notifier : 注册Qos notifier(每一个device的三种Qos都各有一个通知链), 当device的三种Qos值有所改变时, 会发送通知
  • dev_pm_qos_add_global_notifier : 注册一个notifer(dev_pm_notifiers通知链上), 当有任何device的Qos值改变时, 会发送通知