Fork me on GitHub

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\n", i);

			continue;
		}
		pr_debug("table entry %u: %u kHz, %u driver_data\n",
					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的目录。

posted @ 2018-07-23 21:04  yooooooo  阅读(2587)  评论(0编辑  收藏  举报