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的目錄。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM