《Linux设备驱动开发详解:基于最新的Linux 4.0内核》19. Linux电源管理系统架构和驱动 联系客服

发布时间 : 星期三 文章《Linux设备驱动开发详解:基于最新的Linux 4.0内核》19. Linux电源管理系统架构和驱动更新完毕开始阅读14578ec60b4e767f5acfceee

15 continue;

16 if (s->target_residency > data->predicted_us) 17 continue;

18 if (s->exit_latency > latency_req) 19 continue;

20 if (s->exit_latency * multiplier > data->predicted_us) 21 continue; 22

23 if (s->power_usage < power_usage) { 24 power_usage = s->power_usage; 25 data->last_state_idx = i;

26 data->exit_us = s->exit_latency; 27 } 28 } 29

30 return data->last_state_idx; 31 }

还是回到drivers/media/platform/via-camera.c,当摄像头关闭后,它会通过如下语句告知上述对PM_QOS_CPU_DMA_LATENCY的性能要求取消:

static int viacam_streamon(struct file *filp, void *priv, enum v4l2_buf_type t) {

pm_qos_remove_request(&cam->qos_request); … }

类似的设备驱动中申请QoS特性的例子还包括drivers/net/wireless/ipw2x00/ipw2100.c,drivers/tty/serial/omap-serial.c,drivers/net/ethernet/intel/e1000e/netdev.c等。

应用程序则可以通过向/dev/cpu_dma_latency和/dev/network_latency这样的设备节点写入值来发起QoS的性能请求。

8. CPU热插拔

Linux CPU热插拔的功能已经存在了相当长的时间,在Linux 3.8之后的内核一个小小的改进就是CPU0也可以热插拔了。

一般来讲,在用户空间可以透过/sys/devices/system/cpu/cpun/online结点来操作一个CPU的online和offline:

# echo 0 > /sys/devices/system/cpu/cpu3/online CPU 3 is now offline

# echo 1 > /sys/devices/system/cpu/cpu3/online

透过“echo 0 > /sys/devices/system/cpu/cpu3/online”关闭CPU3的时候,CPU3上的进程都会被迁移到其他的CPU上,保证这个拔除CPU3的过程中,系统仍然能正常运行。一旦透过“echo 1 > /sys/devices/system/cpu/cpu3/online”再次开启CPU3,CPU3又可以参与系统的负载均衡,分担系统中的任务。

在嵌入式系统中,CPU热插拔可以作为一种省电的方式,在系统负载小的时候,动态关闭CPU,在系统负载增大的时候,再开启之前offline的CPU。目前各个芯片公司可能会根据自身SoC的特点,对内核进行调整,来实现运行时“热插拔”。这里以Nvidia的Tegra3为例展开。

Tegra3采用vSMP(variable Symmetric Multiprocessing)架构,共5个cortex-a9处理器,其中4个为高性能设计的G核,1个为低功耗设计的LP核,如图19.7。

图19.7 Tegra3的架构

在系统运行过程中,Tegra3的Linux内核会根据CPU负载切换低功耗处理器和高功耗处理器。除此之外,4个高性能ARM核心也会根据运行情况,动态借用Linux kernel支持的CPU hotplug进行CPU的UP/DOWN操作。

用华硕EeePad运行高负载、低负载应用,通过dmesg查看内核消息也确实验证了多核的热插拔以及G核和LP核之间的动态切换:

<4>[104626.426957] CPU1: Booted secondary processor

<7>[104627.427412] tegra CPU: force EDP limit 720000 kHz <4>[104627.427670] CPU2: Booted secondary processor <4>[104628.537005] stop_machine_cpu_stop cpu=0 <4>[104628.537017] stop_machine_cpu_stop cpu=2 <4>[104628.537059] stop_machine_cpu_stop cpu=1

<4>[104628.537702] __stop_cpus: wait_for_completion_timeout+

<4>[104628.537810] __stop_cpus: smp=0 done.executed=1 done.ret =0- <5>[104628.537960] CPU1: clean shutdown

<4>[104630.537092] stop_machine_cpu_stop cpu=0 <4>[104630.537172] stop_machine_cpu_stop cpu=2

<4>[104630.537739] __stop_cpus: wait_for_completion_timeout+

<4>[104630.538060] __stop_cpus: smp=0 done.executed=1 done.ret =0- <5>[104630.538203] CPU2: clean shutdown <4>[104631.306984] tegra_watchdog_touch

高性能处理器和低功耗处理器切换:

<3>[104666.799152] LP=>G: prolog 22 us, switch 2129 us, epilog 24 us, total 2175 us <3>[104667.807273] G=>LP: prolog 18 us, switch 157 us, epilog 25 us, total 200 us <4>[104671.407008] tegra_watchdog_touch

<4>[104671.408816] nct1008_get_temp: ret temp=35C

<3>[104671.939060] LP=>G: prolog 17 us, switch 2127 us, epilog 22 us, total 2166 us <3>[104672.938091] G=>LP: prolog 18 us, switch 156 us, epilog 24 us, total 198 us

运行过程中,我们会发现4个G core会动态热插拔,而4个G core和1个LP core之间,会根据运行负载进行cluster切换。这一部分都是在内核里面实现,和tegra的cpufreq驱动(DVFS驱动)紧密关联。相关代码可见于

http://nv-tegra.nvidia.com/gitweb/?p=linux-2.6.git;a=tree;f=arch/arm/mach-tegra;h=e5d1ff2;hb=rel-14r7

如何判断自己是什么core

每个core都可以通过调用is_lp_cluster()来判断当前执行CPU是LP还是G处理器:

static inline unsigned int is_lp_cluster(void) {

unsigned int reg;

reg =readl(FLOW_CTRL_CLUSTER_CONTROL); return (reg& 1); /* 0 == G, 1 == LP*/ }

即读FLOW_CTRL_CLUSTER_CONTROL寄存器判断出来自己是G core还是LP core。 G core和LP core cluster的切换时机

[场景1]何时从LP切换给G:当前执行于LPcluster,CPUFreq驱动判断出LP需要升频率超过高值门限,即TEGRA_HP_UP:

case TEGRA_HP_UP:

if(is_lp_cluster() && !no_lp) {

if(!clk_set_parent(cpu_clk, cpu_g_clk)) {

hp_stats_update(CONFIG_NR_CPUS, false); hp_stats_update(0, true);

/* catch-upwith governor target speed */ tegra_cpu_set_speed_cap(NULL); }

[场景2]何时从G切换给LP:当前执行于Gcluster,CPUFreq驱动判断出某G core需要降频率到小于低值门限,即TEGRA_HP_DOWN,且最慢的CPUID不小于nr_cpu_ids(实际上代码逻辑跟踪等价于只有CPU0还活着):

case TEGRA_HP_DOWN:

cpu= tegra_get_slowest_cpu_n(); if(cpu < nr_cpu_ids) { ...

}else if (!is_lp_cluster() && !no_lp) {

if(!clk_set_parent(cpu_clk, cpu_lp_clk)) {

hp_stats_update(CONFIG_NR_CPUS, true); hp_stats_update(0, false);

/* catch-upwith governor target speed */ tegra_cpu_set_speed_cap(NULL); } else

queue_delayed_work(

hotplug_wq, &hotplug_work, down_delay); } break;

切换实际上就发生在clk_set_parent()更改CPU的父时钟里面,这部分代码写地比较丑,1个函数完成n个功能,实际不仅切换了时钟,还切换了G和LP cluster:

clk_set_parent(cpu_clk, cpu_lp_clk) ->

tegra3_cpu_cmplx_clk_set_parent(structclk *c, struct clk *p) ->

tegra_cluster_control(unsigned int us, unsigned int flags) -> tegra_cluster_switch_prolog()-> tegra_cluster_switch_epilog()

G core动态热插拔

何时进行G core的动态plug和unplug: [场景3]当前执行于G cluster,CPUFreq驱动判断出某Gcore需要降频率到小于低值门限,即TEGRA_HP_DOWN,且最慢的CPUID小于 nr_cpu_ids(实际上等价于还有2个或2个以上的G core活着),关闭最慢的CPU,留意tegra_get_slowest_cpu_n()不会返回0,意味着CPU0要么活着,要么切换给LP(对应场景2):

case TEGRA_HP_DOWN:

cpu= tegra_get_slowest_cpu_n(); if(cpu < nr_cpu_ids) { up = false;

queue_delayed_work(

hotplug_wq,&hotplug_work, down_delay); hp_stats_update(cpu, false); }

[场景4]当前执行于G cluster,CPUFreq驱动判断出某Gcore需要设置频率大于高值门限,即TEGRA_HP_UP,如果负载平衡状态为 TEGRA_CPU_SPEED_BALANCED,再开一个core;如果状态为TEGRA_CPU_SPEED_SKEWED,则关一个core。 TEGRA_CPU_SPEED_BALANCED的含义是当前所有Gcore要求的频率都高于最高频率的 50%,TEGRA_CPU_SPEED_SKEWED的含义是当前至少有2个Gcore要求的频率低于门限的25%,即CPU 频率的要求在各个core间有倾斜。

case TEGRA_HP_UP:

if (is_lp_cluster() && !no_lp) { ... } else {

switch (tegra_cpu_speed_balance()) {

/* cpu speed is up and balanced - one more on-line */ case TEGRA_CPU_SPEED_BALANCED:

cpu =cpumask_next_zero(0, cpu_online_mask); if (cpu

hp_stats_update(cpu, true); }

break;

/* cpu speed is up, but skewed - remove one core */ case TEGRA_CPU_SPEED_SKEWED:

cpu =tegra_get_slowest_cpu_n(); if (cpu < nr_cpu_ids) { up =false;

hp_stats_update(cpu, false); }

break;

/* cpu speed is up, butunder-utilized - do nothing */ case TEGRA_CPU_SPEED_BIASED: default:

break; } }

上述代码中TEGRA_CPU_SPEED_BIASED路径的含义是有1个以上Gcore的频率低于最高频率的50%但是未形成SKEWED条件,即只是“BIASED”,还没有达到“SKEWED”的程度,所以暂时什么都不做。 目前,ARM和Linux社区都在从事关于big.LITTLE架构下,CPU热插拔以及调度器方面有针对性的改进。在big.LITTLE架构中,将高性能功耗也较高的Cortex-A15和稍低性能功耗低的Cortex-A7进行了结合,或者在64位下,进行Cortex-A57和Cortex-A53的组合,如图19.8。