devfreq 內核框架介紹【轉】

轉自:https://blog.csdn.net/weixin_39059738/article/details/104260671

目錄

一 應用背景

二 軟件框架介紹

三 API和用戶接口

3.1 device註冊接口介紹

3.2 governor使用接口介紹

3.2.1 governor註冊接口

3.2.2 governor調頻接口

3.2.3 monitor機制接口

3.3 用戶層接口

四 編程模板

4.1 dts配置

4.1.1 定義工作性能表:

4.1.2 引用工作性能表:

4.2 底層driver實現

4.2.1 底層driver需要實現的回調函數

4.2.2 probe時註冊devfreq框架

4.3 governor的註冊

4.3.1 governor需要實現的回調函數

4.3.2 governor向devfreq的註冊

五 框架分析

5.1 devfreq框架初始化

5.2 對device的管理

一 應用背景
對於可以調頻的設備,我們可以通過在不同場景和需求下調節設備的頻率,可以達到節省功耗的目的。CPU有單獨的cpufreq框架來控制和管理其頻率,但是不兼容普通的設備。對於普通設備,我們可以通過devfreq 框架來方便地實現調頻操作。

二 軟件框架介紹
devfreq框架存在的意義,是爲了將調頻邏輯的公共部分,比如數據結構,調頻方法等抽象出來,減少重複代碼的產生,方便驅動工程師實現設備的調頻操作。

我們這裏將有調頻需求的設備稱爲device_freq,以便後續的討論。有了devfreq框架,驅動工程師只需要按照devfreq框架提供的函數原型,實現具體設備的具體調頻操作,同時選取合適的governor,並將device_freq和底層調頻方法一同註冊進devfreq框架,就能夠實現調頻。

這裏的governor ,其實指的是不同的調頻策略。所謂的調頻策略,包括什麼時候對設備進行調頻,以及將設備的頻率調整到多大。不同的設備,有不同的調頻策略,因此devfreq將設備的調頻策略抽象出來,以供設備選擇。具體的設備也可以根據自己的需求,實現自己的governor,並註冊進devfreq框架。以內核提供的最簡單的userspace_governor爲例 ,其調頻策略就很簡單——調頻的時機是當用戶通過給定的文件節點輸入目標頻率F時,就執行調頻操作;且將設備的頻率調整爲F。

以上提到的兩個概念,device_freq和 governor,是devfreq框架的兩個核心主題,這裏做一個總結:

1. device_freq,是需要調頻的設備,需要通過devfreq框架提供的接口進行註冊;

2. governor,是具體的調頻策略,也需要通過devfreq框架提供的接口進行註冊;governor可由用戶自己實現,也可以選用內核已有的governor。

整體上,devfreq的軟件框架如下所示:

 

 

 

上圖中,devfreq框架內部維護了兩個鏈表,一個鏈表Governor_list是用於管理系統中所有註冊進來的governor,還有一個devfreq_list 是用於管理系統中所有的可調頻的設備device_freq。

一個擁有opp_table的可調頻設備,需要實現自己的調頻函數(圖中的xxx_devfreq_profile),再通過devfreq提供的接口(devm_devfreq_add_device)來註冊進devfreq框架。在註冊時,device_freq會指定自己的governor,devfreq框架負責將device_freq和governor進行匹配 。

用戶可以實現自己的governor,並通過接口(devfreq_add_governor)註冊進devfreq框架。

三 API和用戶接口
3.1 device註冊接口介紹
device_freq通過接口devm_devfreq_add_device註冊進devfreq框架:該接口的定義如下所示:

struct devfreq *devm_devfreq_add_device(struct device *dev,
struct devfreq_dev_profile *profile,
const char *governor_name,
void *data);
設備在調用該函數時,device_freq需要傳遞四個參數:

1. @dev:設備的device結構體

2. @governor_name:調頻設備必須指定一個governor,因爲沒有governor就無法實現調頻。device_freq註冊時,devfreq框架會根據該字段自動尋找匹配的governor ,將二者關聯。

3. @data:該字段用於governor和device_freq之間傳遞信息,由用戶自己定義,框架並不關心這個數據。

4. @profile: 該profile是一個結構體,類似於去銀行註冊銀行卡時填寫的信息表,定義如下(講解見註釋):

struct devfreq_dev_profile {
/* 在註冊時,設備初始的工作頻率,必須 */
unsigned long initial_freq;
/*
* 當governor啓動monitor機制時,monitor會每隔一定的時間更新設備的頻率,這個位段就指定了該
* 時間間隔。當該值爲0時,表示不啓用monitor機制。
*/
unsigned int polling_ms;
/* 底層設備的調頻函數,設備必須自己實現該函數,並在註冊時通過該結構體提供給框架 */
int (*target)(struct device *dev, unsigned long *freq, u32 flags);
/* 該函數目前很少被用到,可以不實現 */
int (*get_dev_status)(struct device *dev,
¦ ¦ struct devfreq_dev_status *stat);
/* 底層設備的獲取當前設備頻率的函數,設備必須自己實現該函數,並提供給框架 */
int (*get_cur_freq)(struct device *dev, unsigned long *freq);
/* 可選的退出函數,當devfreq設備發生錯誤時,框架會回調該函數。一般不需要實現。 */
void (*exit)(struct device *dev);
/*
* 設備支持的頻點表。如果已經在dts中引用了opp_table,並通過dev_pm_opp_of_add_table
* 關聯到opp_table,以下兩個字段會由devfreq自動填入。
*/
unsigned long *freq_table;
/* 該設備最多支持的頻點數 */
unsigned int max_state;
};

可見,該profile結構體包含了device_freq設備在註冊進devfreq框架時需要填寫的所有信息,其中必須提供的信息包括:

初始頻率initial_freq;
設備調頻函數target
設備當前頻率的獲取函數get_cur_freq
另外,devfreq框架需要知道設備支持的所有頻點。因此可以在profile中將freq_table和max_state填寫,註冊的時候由devfreq框架讀取;但是更加規範的方法是在dts中引用一個opp_table,然後在設備註冊進devfreq框架前,調用dev_pm_opp_of_add_table將設備和opp_table相關聯;這樣,devfreq框架會自動讀取設備的opp_table,將信息整合,代替freq_table和 max_state字段。

3.2 governor使用接口介紹
3.2.1 governor註冊接口
governor的註冊接口比較簡單:

int devfreq_add_governor(struct devfreq_governor *governor);
這裏,重點關注傳參類型struct devfreq_governor:

struct devfreq_governor {
struct list_head node;

const char name[DEVFREQ_NAME_LEN];
const unsigned int immutable;
int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
int (*event_handler)(struct devfreq *devfreq,
unsigned int event, void *data);
};
governor結構體由以下幾個部分組成:

節點node,用於加入全局鏈表governor_list
governor的名字,用於在device註冊進來時進行匹配
immutable 標誌位,當置爲1時,表示使用該governor的設備不可以動態更換其他的governor
get_target_freq回調函數,是具體的調頻策略實現,用於給出某個時間點設備的目標調節頻率,也是整個governor的核心操作之一。
event_handler回調函數,用於處理框架發送給governor的事件。典型的事件包括:DEVFREQ_GOV_START,DEVFREQ_GOV_STOP等。例如,當有設備註冊進框架,並匹配到該governor時,devfreq框架調用該函數,並傳遞DEVFREQ_GOV_START事件類型。
3.2.2 governor調頻接口
前面提到,governor負責具體的調頻策略,也就是說,何時進行調頻也是governor決定的。比如,userspace_governor的策略,就是當用戶通過文件節點輸入設備頻率時,開始進行調頻操作。governor可通過調用以下接口進行調頻:

int update_devfreq(struct devfreq *devfreq);
第一眼看到這個函數或許會有疑惑,爲什麼傳參裏面沒有出現目標頻率這個參數?調用完這個函數,設備的頻率會變成多少?事實上,該函數內部會先調用governor->get_target_freq 回調函數獲取governor推薦的目標頻率,然後再調用設備的target回調函數,實現一次具體的調頻操作。

3.2.3 monitor機制接口
devfreq爲governor實現了一種基本的調頻策略,就是每隔一段時間就調用 update_devfreq函數更新設備的頻率,稱之爲monitor機制。monitor機制的相關API包括:

extern void devfreq_monitor_start(struct devfreq *devfreq);
extern void devfreq_monitor_stop(struct devfreq *devfreq);
extern void devfreq_monitor_suspend(struct devfreq *devfreq);
extern void devfreq_monitor_resume(struct devfreq *devfreq);
start接口用於開啓monitor模式,可以在devfreq向governor 發送DEVFREQ_GOV_START事件時調用該函數;

stop接口用於開關閉monitor模式,可以在devfreq向governor 發送DEVFREQ_GOV_STOP事件時調用該函數;

suspend接口用於暫停monitor模式,可以在devfreq向governor 發送DEVFREQ_GOV_SUSPEND事件時調用該函數;

resume接口用於暫停monitor模式,可以在devfreq向governor 發送DEVFREQ_GOV_RESUME事件時調用該函數;

3.3 用戶層接口
所有註冊進devfreq框架的設備,都會在/sys/class/devfreq/xxxx-device/下生成以下文件節點:

節點名稱 讀操作 寫操作
governor 返回device的governor名稱 寫入目標governorB的名字,可以替換當前governorA
available governors 獲取所有支持的governor名字 不支持
cur_freq 獲取設備當前的頻率 不支持
target_freq 獲取上一個使用的頻點 不支持
polling_interval 獲取當前值 修改當前值
min_freq 獲取設備支持的最小值 修改設備支持的最小值
max_freq 獲取設備支持的最大值 修改設備支持的最大值
available frequencies 獲取設備支持的所有頻點信息 不支持
trans_state 獲取設備的調頻歷史紀錄 不支持
四 編程模板
4.1 dts配置
4.1.1 定義工作性能表:
對於使用devfreq框架的設備,都需要在dts中預先定義一個工作性能表。例如,對於可以工作在三個頻點上的設備,可以在dts中預先定義以下的性能工作表:

xxx_opp_table: xxx_opp_table {
compatible = "operating-points-v2";

opp@800000000 {
opp-hz = /bits/ 64 <800000000>;
};
opp@3200000000 {
opp-hz = /bits/ 64 <3200000000>;
};
opp@3800000000 {
opp-hz = /bits/ 64 <3800000000>;
};
};
內核在初始化的過程中,會通過opp框架,將以上的性能表添加至一個全局的鏈表中。

 

4.1.2 引用工作性能表:
定義好性能表後,在device對應的dts中引用對應的性能表:

xxx: xxx_dev {
...;
operating-points-v2 = <&xxx_opp_table>;
}
4.2 底層driver實現
4.2.1 底層driver需要實現的回調函數
底層driver必須實現兩個回調函數:

1. 調頻動作的具體實現,target函數;

2. 當前device的工作頻率的獲取,get_cur_freq函數;

例程如下所示:

static int xxx_freq_target(struct device *dev, unsigned int *freq, u32 flags)
{
struct dev_pm_opp *opp;
unsigned long rate;
rcu_read_lock();
/* 根據參數freq,遍歷device引用的性能表,匹配最接近的性能點opp */
opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(opp)) {
rcu_read_unlock();
printk(KERN_ERR "failed to find opp for %lu hz\n", *freq);
return PTR_ERR(opp);
}
/* 獲取性能點opp中的頻率信息 */
rate = dev_pm_opp_get_freq(opp);
rcu_read_unlock();

/* 根據具體調頻的方法,進行調頻操作 */
return xxx_change_rate(rate);
}

static int xxx_get_cur_freq(struct device *dev, unsigned long *freq)
{
unsigned long rate;

/* 根據device具體的方法,獲得當前device的工作頻率 */
rate = xxx_get_freq(...);
*freq = rate;
return 0
}

static struct devfreq_dev_profile xxx_devfreq_profile = {
.polling_ms = 0,
.target = xxx_freq_target,
.get_cur_freq = xxx_get_cur_freq,
}

4.2.2 probe時註冊devfreq框架
註冊包含兩步:

1.將device添加至opp框架;

2.將device和其實現的回調函數添加至devfreq框架,並指定governor。

例程代碼如下所示:(這裏governor使用的是userspace)

static int xxx_probe(struct platform_device *pdev)

{
struct device *dev = &pdev->dev;
int ret;

...;
/* 1. 將device和dts中引用的opp表相關聯 */
ret = dev_pm_opp_of_add_table(dev);
if (ret)
printk(KERN_ERR, "cannot add opp table!\n");

xxx_devfreq_profile.initial_freq = xxxx;
/* 2. 將device註冊到devfreq框架, governer設置爲userspace */
ret = devm_devfreq_add_device(dev, &xxx_devfreq_profile, "userspace", NULL);
if (ret)
printk(KERN_ERR, "cannot add device to devfreq!\n");
...;

}

4.3 governor的註冊
對於一個支持dvfs的設備,若想通過devfreq框架管理該設備的調頻調壓,必須同時實現該設備的governor ,因爲核心的調頻操作,需要governor的調頻策略,給出目標頻率。

下面以governor_userspace爲例,說明governor的註冊方法。

4.3.1 governor需要實現的回調函數
governor在向框架註冊時,需要提前準備好兩個回調函數:

1.get_target_freq:該函數用於實現具體的調頻策略,給出governor建議的目標調節頻率。對於userspace_governor來說,其調頻策略很簡單,目標頻率就是用戶通過文件節點輸入的頻率。

2.event_handler:該函數用於處理devfreq框架向governor發送的事件。典型的事件包括:

當有設備註冊進devfreq框架,且該設備的governor設定爲該governor時,框架向governor發送DEVFREQ_GOV_START事件。
當有設備從devfreq框架註銷,且該設備的governor設定爲該governor時,框架向governor發送DEVFREQ_GOV_STOP事件。
...
以上兩個回調函數,governor_userspace的實現代碼如下所示:

struct userspace_data {
unsigned long user_frequency;
bool valid;
};

/* userspace_governor給出的目標頻率來自用戶層的輸入 */
static int devfreq_userspace_func(struct devfreq *df, unsigned long *freq)
{
struct userspace_data *data = df->data;

/* 每次當用戶通過文件節點設置目標頻率時,data將被更新 */
if (data->valid)
*freq = data->user_frequency;
else
*freq = df->previous_freq; /* No user freq specified yet */

return 0;
}

/* userspace_governor只處理START和STOP兩個事件 */
static int devfreq_userspace_handler(struct devfreq *devfreq,
unsigned int event, void *data)
{
int ret = 0;

switch (event) {
case DEVFREQ_GOV_START:
ret = userspace_init(devfreq);
break;
case DEVFREQ_GOV_STOP:
userspace_exit(devfreq);
break;
default:
break;
}

return ret;
}

static struct devfreq_governor devfreq_userspace = {
.name = "userspace",
.get_target_freq = devfreq_userspace_func,
.event_handler = devfreq_userspace_handler,
};

其中,我們重點關注userspace_init函數。使用該governor的設備註冊進devfreq框架時,會調用該函數:

static struct attribute *dev_entries[] = {
&dev_attr_set_freq.attr,
NULL,
};
static const struct attribute_group dev_attr_group = {
.name = DEVFREQ_GOV_USERSPACE, //"userspace"
.attrs = dev_entries,
};
static int userspace_init(struct devfreq *devfreq)
{
int err = 0;
struct userspace_data *data = kzalloc(sizeof(struct userspace_data),
¦ ¦ GFP_KERNEL);

if (!data) {
err = -ENOMEM;
goto out;
}
data->valid = false;
devfreq->data = data;

err = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group);
out:
return err;
}

可以看出,userspace_init函數爲使用該governor的設備在/sys文件系統下添加了名爲"userspace"文件夾,並且該文件夾下有一個名爲"set_freq"的節點。通過該節點,我們可以進行調頻操作和讀取當前設備頻率的操作。 相關代碼如下所示:

static ssize_t store_freq(struct device *dev, struct device_attribute *attr,
¦ const char *buf, size_t count)
{
struct devfreq *devfreq = to_devfreq(dev);
struct userspace_data *data;
unsigned long wanted;
int err = 0;

mutex_lock(&devfreq->lock);
data = devfreq->data;
/* 讀取用戶輸入的目標頻率 */
sscanf(buf, "%lu", &wanted);
data->user_frequency = wanted;
data->valid = true;
/* 調用核心的調頻函數,對該設備進行調頻 */
err = update_devfreq(devfreq);
if (err == 0)
err = count;
mutex_unlock(&devfreq->lock);
return err;
}

static ssize_t show_freq(struct device *dev, struct device_attribute *attr,
¦char *buf)
{
struct devfreq *devfreq = to_devfreq(dev);
struct userspace_data *data;
int err = 0;

mutex_lock(&devfreq->lock);
data = devfreq->data;

if (data->valid)
err = sprintf(buf, "%lu\n", data->user_frequency);
else
err = sprintf(buf, "undefined\n");
mutex_unlock(&devfreq->lock);
return err;
}

static DEVICE_ATTR(set_freq, 0644, show_freq, store_freq);
4.3.2 governor向devfreq的註冊
當準備好以上回調函數後,就可以將通過devfreq提供的接口devfreq_add_governor,將governor註冊進devfreq框架。以governor_userspace爲例,governor的註冊方法如下:

static int __init devfreq_userspace_init(void)
{
return devfreq_add_governor(&devfreq_userspace);
}
subsys_initcall(devfreq_userspace_init);
五 框架分析
5.1 devfreq框架初始化
devfreq框架的初始化很簡單,只是在sys/class/目錄下創建了名爲devfreq的類,並指定了該類下面的所有device所具備的文件屬性。具體代碼如下所示:

static int __init devfreq_init(void)
{
/* 創建devfreq類 */
devfreq_class = class_create(THIS_MODULE, "devfreq");
if (IS_ERR(devfreq_class)) {
pr_err("%s: couldn't create class\n", __FILE__);
return PTR_ERR(devfreq_class);
}
/* 創建工作隊列 */
devfreq_wq = create_freezable_workqueue("devfreq_wq");
if (!devfreq_wq) {
class_destroy(devfreq_class);
pr_err("%s: couldn't create workqueue\n", __FILE__);
return -ENOMEM;
}
devfreq_class->dev_groups = devfreq_groups;

return 0;
}
subsys_initcall(devfreq_init);
這樣,所有註冊進devfreq框架的device,在其目錄下都會自動創建以下文件節點:

static struct attribute *devfreq_attrs[] = {
&dev_attr_governor.attr,
&dev_attr_available_governors.attr,
&dev_attr_cur_freq.attr,
&dev_attr_available_frequencies.attr,
&dev_attr_target_freq.attr,
&dev_attr_polling_interval.attr,
&dev_attr_min_freq.attr,
&dev_attr_max_freq.attr,
&dev_attr_trans_stat.attr,
NULL,
};
ATTRIBUTE_GROUPS(devfreq);
5.2 對device的管理
devfreq框架很大一部分代碼是對註冊進來的device的管理。我們可以從3.2.2小節中device向devfreq註冊的接口devm_devfreq_add_device入手,對相關代碼進行分析。

devm_devfreq_add_device函數內部主要調用了函數devfreq_add_device,該函數較長,我們節選核心的部分進行分析:

1. 首先在全局鏈表devfreq_list中,查看該設備是否已經註冊,如果已經註冊,直接返回錯誤:

struct devfreq *devfreq_add_device(struct device *dev,
¦ struct devfreq_dev_profile *profile,
¦ const char *governor_name,
/ ¦ void *data)
{
struct devfreq *devfreq;
struct devfreq_governor *governor;
static atomic_t devfreq_no = ATOMIC_INIT(-1);
int err = 0;

...;
mutex_lock(&devfreq_list_lock);
devfreq = find_device_devfreq(dev);
mutex_unlock(&devfreq_list_lock);
if (!IS_ERR(devfreq)) {
dev_err(dev, "%s: Unable to create devfreq for the device.\n",
__func__);
err = -EINVAL;
goto err_out;
}
...;
2. 然後,爲內部使用的devfreq類型的特殊設備分配內存,並對其進行初始化。這裏,特別注意,註冊進來的設備,成爲內部的devfreq設備的父設備,而且,註冊時 profile必須提供設備的初始頻率 initial_freq:

/* devfreq內部使用結構體struct devfreq來管理設備 */
devfreq = kzalloc(sizeof(struct devfreq), GFP_KERNEL);
if (!devfreq) {
err = -ENOMEM;
goto err_out;
}

mutex_init(&devfreq->lock);
mutex_lock(&devfreq->lock);
/* 其父設備爲註冊進來的device,這樣,內部維護的devfreq設備就和原註冊設備相關聯起來 */
devfreq->dev.parent = dev;
devfreq->dev.class = devfreq_class;
devfreq->dev.release = devfreq_dev_release;
devfreq->profile = profile;
strncpy(devfreq->governor_name, governor_name, DEVFREQ_NAME_LEN);
devfreq->previous_freq = profile->initial_freq;
devfreq->last_status.current_frequency = profile->initial_freq;
devfreq->data = data;
....
3. devfreq框架是如何知道該設備支持哪些頻點的呢?一種方式是device在註冊進來的時候,通過profile結構體,預先設填好freq_table和max_state字段,顯然這種方式比較笨,一般不會使用;還有一種方式,就是由devfreq框架通過設備註冊時的傳參device,使用opp框架的幫助函數來讀取該設備支持的opp_table。相關代碼如下所示:

if (!devfreq->profile->max_state && !devfreq->profile->freq_table) {
mutex_unlock(&devfreq->lock);
err = set_freq_table(devfreq);
if (err < 0)
goto err_dev;
mutex_lock(&devfreq->lock);
}
其中核心的處理函數set_freq_table,首先通過dev_pm_opp_get_opp_count獲取設備的頻點數,將該頻點數設置爲max_state的值。再動態分配freq_table,使用dev_pm_opp_find_freq_ceil依次讀取與設備關聯的opp_table中的頻率值,填充進freq_table中。相關代碼如下:

static int set_freq_table(struct devfreq *devfreq)
{
struct devfreq_dev_profile *profile = devfreq->profile;
struct dev_pm_opp *opp;
unsigned long freq;
int i, count;
/* Initialize the freq_table from OPP table */
count = dev_pm_opp_get_opp_count(devfreq->dev.parent);
if (count <= 0)
return -EINVAL;

profile->max_state = count;
profile->freq_table = devm_kcalloc(devfreq->dev.parent,
profile->max_state,
sizeof(*profile->freq_table),
GFP_KERNEL);
if (!profile->freq_table) {
profile->max_state = 0;
return -ENOMEM;
}

for (i = 0, freq = 0; i < profile->max_state; i++, freq++) {
opp = dev_pm_opp_find_freq_ceil(devfreq->dev.parent, &freq);
if (IS_ERR(opp)) {
devm_kfree(devfreq->dev.parent, profile->freq_table);
profile->max_state = 0;
return PTR_ERR(opp);
}
dev_pm_opp_put(opp);
profile->freq_table[i] = freq;
}

return 0;
}
4. 根據device的opp_table讀取最大值和最小值:

devfreq->scaling_min_freq = find_available_min_freq(devfreq);
if (!devfreq->scaling_min_freq) {
mutex_unlock(&devfreq->lock);
err = -EINVAL;
goto err_dev;
}
devfreq->min_freq = devfreq->scaling_min_freq;

devfreq->scaling_max_freq = find_available_max_freq(devfreq);
if (!devfreq->scaling_max_freq) {
mutex_unlock(&devfreq->lock);
err = -EINVAL;
goto err_dev;
}
devfreq->max_freq = devfreq->scaling_max_freq;
5. 根據profile指定的名字綁定具體的governor,並將新創建的devfreq添加至全局的鏈表統一管理:

governor = try_then_request_governor(devfreq->governor_name);
if (IS_ERR(governor)) {
dev_err(dev, "%s: Unable to find governor for the device\n",
__func__);
err = PTR_ERR(governor);
goto err_init;
}

devfreq->governor = governor;
err = devfreq->governor->event_handler(devfreq, DEVFREQ_GOV_START,
NULL);
if (err) {
dev_err(dev, "%s: Unable to start governor for the device\n",
__func__);
goto err_init;
}

list_add(&devfreq->node, &devfreq_list);
//TODO增加對governor管理的文檔;增加統一文件節點的介紹
————————————————
版權聲明:本文爲CSDN博主「華華要好好學真正的技術啦」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_39059738/article/details/104260671

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章