zoukankan      html  css  js  c++  java
  • CPUFreq驱动

    CPUFreq子系统位于 drivers/cpufreq目录下,负责进行运行过程中CPU频率和电压的动态调整,即DvFS( Dynamic Voltage Frequency Scaling,动态电压频率调整)。运行时进行CPU电压和频率调整的原因是:CMOS电路中的功耗与电压的平方成正比、与频率成正比(P∝fV2)因此降低电压和频率可降低功耗。
    CPUFreq的核心层位于drivers/cpufreq/cpufreq,c下,它为各个SoC的CPUFreq驱动的实现提供了一套统一的接口,并实现了一套notifier机制,可以在 CPUFreq的策略和频率改变的时候向其他模块发出通知。另外,在CPU运行频率发生变化的时候,内核的 loops perify常数也会发生相应变化。

    SOC的CPUFreq驱动实现

    每个SoC的具体CPUFreq驱动实例只需要实现电压、频率表,以及从硬件层面完成这些变化。
    CPUFreq核心层提供了如下API以供SoC注册自身的CPUFreq驱动:
    int cpufreq_register_driver(struct cpufreq_driver *driver_data)
    其参数为一个cpufreq_driver结构体指针,实际上,cpufreq_driver封装了一个具体的SoC的CPUFreq驱动的主体,该结构体形如代码如下所示。

    struct cpufreq_driver {
    	char			name[CPUFREQ_NAME_LEN];
    	u8			flags;
    
    	/* needed by all drivers */
    	int	(*init)		(struct cpufreq_policy *policy);
    	int	(*verify)	(struct cpufreq_policy *policy);
    
    	/* define one out of two */
    	int	(*setpolicy)	(struct cpufreq_policy *policy);
    	int	(*target)	(struct cpufreq_policy *policy,	/* Deprecated */
    				 unsigned int target_freq,
    				 unsigned int relation);
    	int	(*target_index)	(struct cpufreq_policy *policy,
    				 unsigned int index);
    
    	/* should be defined, if possible */
    	unsigned int	(*get)	(unsigned int cpu);
    
    	/* optional */
    	int	(*bios_limit)	(int cpu, unsigned int *limit);
    
    	int	(*exit)		(struct cpufreq_policy *policy);
    	int	(*suspend)	(struct cpufreq_policy *policy);
    	int	(*resume)	(struct cpufreq_policy *policy);
    	struct freq_attr	**attr;
    };
    
    

    其中的 owner成员一般被设置为 THIS MODULE;name成员是CPUFreq驱动的名字,如drivers/cpufreq/s5pv210-cpufreq.c设置name为s5pv210, drivers/cpufreq/omap-cpufreq.c设置name为omap;falgs是一些暗示性的标志,譬如,若设置了 CPUFREQ_CONST_LOOPS,则是告诉内核loops_per_jiffy不会因为CPU频率的变化而变化。
    init()成员是一个per-CPU初始化函数指针,每当一个新的CPU被注册进系统的时候,该函数就被调用,该函数接受一个cpufreq_policy的指针参数,在init()成员函数中,可进行如下设置:

    policy->cpuinfo.min_freq
    policy->cpuinfo.max_freq
    

    上述代码描述的是该CPU支持的最小频率和最大频率(单位是kHz)。

    policy->cur
    

    上述代码描述的是CPU的当前频率;

    policy->policy
    policy->governor
    policy->min
    policy->max
    

    上述代码定义该CPU的缺省策略,以及在缺省策略情况下,该策略支持的最小、最大CPU频率。
    verify成员函数用于对用户的 CPUFreq策略设置进行有效性验证和数据修正。每当用户设定一个新策略时,该函数根据老的策略和新的策略,检验新策略设置的有效性并对无效设置进行必要的修正。在该成员函数的具体实现中,常用到如下辅助函数:

    static inline void
    cpufreq_verify_within_cpu_limits(struct cpufreq_policy *policy)
    {
    	cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
    	policy->cpuinfo.max_freq);
    }
    

    setpolicyo成员函数接受一个policy参数(包含policy->policypolicy->minpolicy->max等成员),实现了这个成员函数的CPU一般具备在一个范围(limit,从policy->minpolicy->max)里自动调整频率的能力。目前只有少数驱动(如intel_pstate.c和longrun.c)包含这样的成员函数,而绝大多数CPU都不会实现此函数,一般只实现target()成员函数,target()的参数直接就是一个指定的频率。
    target()成员函数用于将频率调整到一个指定的值,接受3个参数: policy、 target_freq和relation, target freq是目标频率,实际驱动总是要设定真实的CPU频率到最接近于 target_feq,并且设定的频率必须位于 policy->min到 policy->max之间。在设定频率接近 target_feq的情况下, relation若为 CPUFREQ REL I,则暗示设置的频率应该大于或等于 target_freq; relation若为 CPUFREQ_REL_H,则暗示设置的频率应该小于或等于 target_freq。
    表19.1描述了 setpolicy()和 target()所针对的CPU以及调用方式上的区别。

    setpolicy() target()
    CPU有在一定范围内独立调整频率的能力 CPU只能指定频率
    CPU freq policy 调用到setpolicy(),由CPU独立在一个范围内调整频率 由CPU Freq核心层根据系统负载和策略综合决定目标频率

    根据芯片内部PLL和分频器的关系, ARM SOC一般不具备独立调整频率的能力,往往SoC的 CPUFreq驱动会提供一个频率表,频率在该表的范围内进行变更,因此一般实现target()成员函数。
    CPUFreq核心层提供了一组与频率表相关的辅助API。

    int cpufreq_frequency_table_cpuinfo(struct cpufreq_policy *policy,
    				    struct cpufreq_frequency_table *table)
    {
    	unsigned int min_freq = ~0;
    	unsigned int max_freq = 0;
    	unsigned int i;
    
    	for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) {
    		unsigned int freq = table[i].frequency;
    		if (freq == CPUFREQ_ENTRY_INVALID) {
    			pr_debug("table entry %u is invalid, skipping
    ", i);
    
    			continue;
    		}
    		pr_debug("table entry %u: %u kHz, %u driver_data
    ",
    					i, freq, table[i].driver_data);
    		if (freq < min_freq)
    			min_freq = freq;
    		if (freq > max_freq)
    			max_freq = freq;
    	}
    
    	policy->min = policy->cpuinfo.min_freq = min_freq;
    	policy->max = policy->cpuinfo.max_freq = max_freq;
    
    	if (policy->min == ~0)
    		return -EINVAL;
    	else
    		return 0;
    }
    

    它是 cpufreq driver的init成员函数的助手,用于将policy->min和 policy->max设置为与 cpuinfo->min_freq和 cpuinfo.max_freq相同的值。

    int cpufreq_frequency_table_verify(struct cpufreq_policy *policy,
    				   struct cpufreq_frequency_table *table)
    

    它是 cpufreq driver的verify成员函数的助手,确保至少有1个有效的CPU频率位于policy->min到 policy->max的范围内。

    int cpufreq_frequency_table_target(struct cpufreq_policy *policy,
    				   struct cpufreq_frequency_table *table,
    				   unsigned int target_freq,
    				   unsigned int relation,
    				   unsigned int *index)
    

    CPUFreq的策略

    SoCCPUFreq驱动只是设定了CPU的频率参数,以及提供了设置频率的途径,但是它并不会管CPU自身究竟应该运行在哪种频率上。究竟频率依据的是哪种标准,进行何种变化而这些完全由 CPUFreq的策略( policy)决定,这些策略如表19.2所示。

    CPUFreq的策略 策略实现的方式
    cpufreq_ondemand 平时以低速的方式运行,当系统负载提高需自动提高频率
    cpufreq_performance cpu以最高频率运行,即scaling_max_freq
    cpufreq_consevative 字面含义是传统的、保守的,跟ondemand相似,区别在于动态频率在变更的时候采用渐进的方式
    cpufreq_powersave cpu以最低频率运行,即scaling_min_freq
    cpufreq_userspace 让根用户通过sys节点scaling_setspeed设置频率

    在 Android系统中,则增加了1个交互策略,该策略适合于对延迟敏感的U1交互任务,当有UI交互任务的时候,该策略会更加激进并及时地调整CPU频率。
    总而言之,系统的状态以及CPUFreq的策略共同决定了CPU频率跳变的目标, CPUFreq核心层并将目标频率传递给底层具体SoC的 CPUFreq驱动,该驱动修改硬件,完成频率的变换,如图19.2所示。

    用户空间一般可通过 /sys/devices/system/cpu/cpux/cpufreq节点来设置 CPUFreq。譬如,我们要设置 CPUFreq到700Mhz,采用 userspace策略,则运行如下命令:

    echo userspace >/sys/devices/system/cpu/cpu0/cpufreq/scaling governor
    echo 700000>/sys/devices/system/cpu/cpu/cpufreq/scaling set speed
    

    CPUFreq的性能测试和调优

    使用cpufreq-bench工具可以帮助工程师分析采用CPUFreq后对系统性能的影响;

    CPUFreq通知

    CPUFreq子系统会发出通知的情况有两种:CPUFreq的策略变化或者CPU运行频率变化。
    在策略变化的过程中,会发送3次通知:

    • CPUFREQ ADJUST:所有注册的 notifier可以根据硬件或者温度的情况去修改范围(即 policy->min和 policy->max);
    • CPUFREQ INCOMPATIBLE:除非前面的策略设定可能会导致硬件出错,否则被注册的notifier不能改变范围等设定;
    • CPUFREQ NOTIFY:所有注册的notifier都会被告知新的策略已经被设置。在频率变化的过程中,会发送2次通知;
    • CPUFREQ PRECHANGE:准备进行频率变更;
    • CPUFREQ POSTCHANGE:已经完成频率变更。

    notifier中的第3个参数是一个 cpufreq_freqs的结构体,包含cpu(CPU号)、old(过去的频率)和new(现在的频率)这3个成员。发送 CPUFREQ_PRECHANGE和 CPUFREQ_POSTCHANGE的代码如下:

    int srcu_notifier_call_chain(struct srcu_notifier_head *nh,
    		unsigned long val, void *v)
    	
    

    如果某模块关心 CPUFREQ_PRECHANGE或 CPUFREQ_POSTCHANGE事件,可简单地使用 Linux notifier机制监控。譬如, drivers/video/sallo0fbc在CPU频率变化过程中需对自身硬件进行相关设置,因此它注册了 notifier并在 CPUFREQ _PRECHANGE和CPUFREQ_POSTCHANGE情况下分别进行不同的处理,如代码清单19.3所示。

    #ifdef CONFIG_CPU_FREQ
    	fbi->freq_transition.notifier_call = sa1100fb_freq_transition;
    	fbi->freq_policy.notifier_call = sa1100fb_freq_policy;
    	cpufreq_register_notifier(&fbi->freq_transition, CPUFREQ_TRANSITION_NOTIFIER);
    	cpufreq_register_notifier(&fbi->freq_policy, CPUFREQ_POLICY_NOTIFIER);
    #endif
    
    
    /*
     * CPU clock speed change handler.  We need to adjust the LCD timing
     * parameters when the CPU clock is adjusted by the power management
     * subsystem.
     */
    static int
    sa1100fb_freq_transition(struct notifier_block *nb, unsigned long val,
    			 void *data)
    {
    	struct sa1100fb_info *fbi = TO_INF(nb, freq_transition);
    	struct cpufreq_freqs *f = data;
    	u_int pcd;
    
    	switch (val) {
    	case CPUFREQ_PRECHANGE:
    		set_ctrlr_state(fbi, C_DISABLE_CLKCHANGE);
    		break;
    
    	case CPUFREQ_POSTCHANGE:
    		pcd = get_pcd(fbi->fb.var.pixclock, f->new);
    		fbi->reg_lccr3 = (fbi->reg_lccr3 & ~0xff) | LCCR3_PixClkDiv(pcd);
    		set_ctrlr_state(fbi, C_ENABLE_CLKCHANGE);
    		break;
    	}
    	return 0;
    }
    
    

    此外,如果在系统挂起/恢复的过程中CPU频率会发生变化,则 CPUFreq子系统也会发出CPUFREQ_SUSPENDCHANGE和 CPUFREQ _RESUMECHANGE这两个通知。

    值得一提的是,除了CPU以外,一些非CPU设备也支持多个操作频率和电压,存在多个OPP。Linux3.2之后的内核也支持针对这种非CPU设备的DVFS,该套子系统为Devfreq。与CPUFreq存在一个drivers/cpufreq目录相似,在内核中也存在一个drivers/devore的目录。

  • 相关阅读:
    Error和Exception的区别
    当try和finally都包含return时的执行顺序
    String,StringBuffer处理字符串的区别
    使用idea对XML的增删改查
    IO流,字节流复制文件,字符流+缓冲复制文件
    MySQL同步故障:" Slave_SQL_Running:No" 主从同步的从表进行了写操作
    常用MQ的对比冷知识
    Redis-避免缓存穿透
    Docker容器与虚拟化技术——部署KVM虚拟化平台
    HTML日记 第三篇 关于图片的冷知识(附带一些浮动的基础知识)
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/9355595.html
Copyright © 2011-2022 走看看