Devfreq學習筆記


一、簡介

當今的復雜SoC由多個子模塊協同工作組成。在執行各種用例的操作系統中,並非SoC中的所有模塊都需要始終保持最高性能。為方便起見,將SoC中的子模塊分組為域,從而允許某些域以較低的電壓和頻率運行,而其他域以較高的電壓/頻率對運行。

對於這些設備支持的頻率和電壓對,我們稱之為OPP(Operating Performance Point)。對於具有OPP功能的非CPU設備,本文稱之為OPP device,需要通過devfreq進行動態的調頻調壓。

devfreq:Generic Dynamic Voltage and Frequency Scaling (DVFS) Framework for Non-CPU Devices。是由三星電子MyungJoo Ham <myungjoo.ham@samsung.com>,提交到社區。原理和/deivers/cpufreq 非常近似。但是cpufreq驅動並不允許多個設備來注冊,而且也不適合不同的設備具有不同的governor。devfreq則支持多個設備,並且允許每個設備有自己對應的governor。

如下圖,devfreq framework是功耗子系統的一部分,與cpufreq,cpuidle,powermanager相互配合協作,已達到節省系統功耗的目的。

 

二、核心數據結構

devfreq framework作為Linux Kernel一個子系統,需要為user space和其他子系統提供接口,已完成功能交互。本章從Kernel空間和user空間的角度,介紹devfreq framework的相關接口。

(1) devfreq_dev_profile 結構體,是OPP device注冊到devfreq framework的數據結構,主要包含OPP設備的頻率相關信息和相關的回調函數,是devfreq framework和OPP device driver的交互接口。類似設備驅動模型,將OPP device driver對devfreq使用,簡化為devfreq profile結構體的填充。

struct devfreq_dev_profile {
    /*devfreq初始化頻率*/
    unsigned long initial_freq;
    /*governor輪詢的時間間隔,單位ms,0禁止*/
    unsigned int polling_ms;

    /*devfreq framework設置OPP device頻率的回掉函數*/
    int (*target)(struct device *dev, unsigned long *freq, u32 flags);
    /*devfreq framework獲取OPP device負載狀態的回掉函數*/
    int (*get_dev_status)(struct device *dev, struct devfreq_dev_status *stat);
    /*devfreq framework獲取OPP device當前頻率的回掉函數*/
    int (*get_cur_freq)(struct device *dev, unsigned long *freq);
    /*devfreq framework退出時對OPP device的回掉函數*/
    void (*exit)(struct device *dev);

    /*OPP device支持的頻率表*/
    unsigned long *freq_table;
    /*freq_table表的大小*/
    unsigned int max_state;
};

(2) devfreq_governor 結構體,是governor注冊到devfreq framework的數據結構,主要包含governor的相關屬性和具體的函數實現。是devfreq framework和governor交互接口。

struct devfreq_governor {
    struct list_head node;

    /*該governor的名稱*/
    const char name[DEVFREQ_NAME_LEN];
    /*governor是否可以切換的標志,若為1表示不可切換*/
    const unsigned int immutable;
    /*governor注冊到devfreq framework的算法實現函數,返回調整后的頻率*/
    int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
    /*governor注冊到devfreq framework的event處理函數,處理start,stop,suspend,resume等event*/
    int (*event_handler)(struct devfreq *devfreq, unsigned int event, void *data);
};

(3) devfreq 設備結構體,這個是devfreq設備的核心數據結構。將上述的OPP device driver的 devfreq_dev_profile 和governor的 devfreq_governor 連接到一起,並通過設備驅動模型中device類,為user空間提供接口。

struct devfreq {
    struct list_head node;

    struct mutex lock;
    struct mutex event_lock;
    /*其class屬於devfreq_class,父節點指向使用devfreq的device*/
    struct device dev;
    /*OPP device注冊到devfreq framework的配置信息*/
    struct devfreq_dev_profile *profile;
    /*governor注冊到devfreq framework的配置信息*/
    const struct devfreq_governor *governor;
    /*devfreq的governor的名字*/
    char governor_name[DEVFREQ_NAME_LEN];
    struct notifier_block nb;
    /*負載監控使用的delayed_work*/
    struct delayed_work work;

    unsigned long previous_freq;
    struct devfreq_dev_status last_status;
    /*OPP device傳遞給governor的私有數據*/
    void *data; /* private data for governors */

    ......
};

 

三、工作流程

本章節主要介紹devfreq framework在系統中的工作流程,從初始化,頻率調整,退出機制幾個方便介紹devfreq framework的運行機制和函數調用邏輯。

1.初始化

(1) evfreq framework 初始化。

(2) governor初始化,然后add_governor。//int devfreq_add_governor(struct devfreq_governor *governor)。

(3) OPP device創建devfreq,然后add_device。//struct devfreq *devfreq_add_device(struct device *dev, struct devfreq_dev_profile *profile, const char *governor_name, void *data)


(1) evfreq framework 初始化

邏輯非常簡單清晰,主要完成以下的任務,然后為governor的初始化和OPP device 創建devfreq device做好准備。代碼實現:kernel/drivers/devfreq/

a.創建devfreq設備類。

b.創建工作隊列,用於負載監控work調用運行。

c.加入到subsys_initcall,系統啟動時初始化。

static int __init devfreq_init(void) //drivers/devfreq/devfreq.c
{
    /*創建devfreq設備類*/
    devfreq_class = class_create(THIS_MODULE, "devfreq");
    /*創建名為"devfreq"的工作隊列,用於負載監控work的調度運行*/
    devfreq_wq = create_freezable_workqueue("devfreq_wq");

    devfreq_class->dev_groups = devfreq_groups;
}
subsys_initcall(devfreq_init);

 

(2) governors初始化

系統中可支持多個governors,在系統啟動時進行初始化,並注冊到devfreq framework中, 后續OPP device創建devfreq設備,會根據governor名字從已經初始化好的governor 列表中,查找對應的governor實例。

a.填充governor的結構體,不同類型的governor,會有不同的實現。

b.將governor加入到devfreq framework的governor列表中。

c.加入到subsys_initcall,系統啟動時初始化。

下面這個是以 simple_ondemand 這個governor來介紹governor的初始化和注冊。

static struct devfreq_governor devfreq_simple_ondemand = {
    .name = DEVFREQ_GOV_SIMPLE_ONDEMAND, //"simple_ondemand"
    .get_target_freq = devfreq_simple_ondemand_func,
    .event_handler = devfreq_simple_ondemand_handler,
};

static int __init devfreq_simple_ondemand_init(void)
{
    return devfreq_add_governor(&devfreq_simple_ondemand);
}
subsys_initcall(devfreq_simple_ondemand_init);

目前系統默認支持下面幾種governor:

simple_ondemand:按需調整模式;根據系統負載動態頻率,平衡性能和功耗

Performance:性能優先模式,調整到最大頻率

Powersave:功耗優先模式,調整到最小頻率

Userspace:用戶指定模式,調整到用戶設置的頻率.

Passive:被動模式,使用設備指定方法做調整或跟隨父devfreq設備的governor

可以根據自己的設備特性,通過填充 devfreq_governor 結構體和實現 get_target_freq 方法和 event_handler,完成的自己的governor。devfreq framework的架構設計對governor擴展,支持極好。

 

(3) OPP device通過devfreq framework創建devfreq device。

以UFS設備為例,介紹OPP device用devfreq framework來添加devfreq設備的過程。代碼位置:kernel/drivers/scsi/ufs/ufshcd.c

首先,OPP device driver通過devfreq framework創建devfreq設備。

a.將ufs設備的core clk的添加到opp子系統中,devfreq_add_device 函數中,會從opp子系統中獲取clk信息。

b.將OPP device相關頻率信息和回調函數,並填充devfreq profile數據結構。

c.調用devfreq_add_device函數,將devfreq profile數據結構,governor名字,添加到devfreq framework。

static int ufshcd_devfreq_init(struct ufs_hba *hba)
{
    struct devfreq *devfreq;
    struct ufs_clk_scaling *scaling = &hba->clk_scaling;
    ......

    /*將可用最大最小頻率添加到OPP子系統*/
    clki = list_first_entry(clk_list, struct ufs_clk_info, list);
    dev_pm_opp_add(hba->dev, clki->min_freq, 0);
    dev_pm_opp_add(hba->dev, clki->max_freq, 0);

    /*初始化devfreq配置結構體*/
    scaling->profile.polling_ms = 60;
    scaling->profile.target = ufshcd_devfreq_target;
    scaling->profile.get_dev_status = ufshcd_devfreq_get_dev_status;

    /*通過devfreq_add_device函數,配置文件和governor,創建devfreq設備*/
    devfreq = devfreq_add_device(hba->dev, &scaling->profile, DEVFREQ_GOV_SIMPLE_ONDEMAND, gov_data);

    ......
}

devfreq_add_device 創建devfreq設備的流程如下:

a. 根據傳入的governor名字,從governor列表中,獲取對應的governor實例。

b.發送 DEVFREQ_GOV_START 到governor,開始管理OPP device的頻率。

struct devfreq *devfreq_add_device(struct device *dev, struct devfreq_dev_profile *profile, const char *governor_name, void *data)
{
    ......
    /*通過governor的名字找到governor實例*/
    governor = find_devfreq_governor(devfreq->governor_name);
    ......
    /*發送DEVFREQ_GOV_START事件,governor開始管理頻率*/
    devfreq->governor->event_handler(devfreq, DEVFREQ_GOV_START, NULL);
    ......
}

 

2. 頻率調整過程

如上圖所示,頻率調整過程中,分工非常明確,devfreq framework是大管家負責監控程序的運行,governor提供管理算法,OPP device提供自身的負載狀態和頻率設置的方法實現。系統中有不同的governor,而且不同的governor有不同的管理算法,但是頻率調整過程是一樣的。本小節,以 simple_ondemand governor 為例,講述governor的管理過程。

(1) governor 的 event_handler 收到 DEVFREQ_GOV_START 事件,調用對應的函數,調度工作隊列,運行負載監控程序。

static int devfreq_simple_ondemand_handler(struct devfreq *devfreq, unsigned int event, void *data)
{
    switch (event) {
    case DEVFREQ_GOV_START:
        devfreq_monitor_start(devfreq); /*對應的DEVFREQ_GOV_START事件的處理函數*/
        break;

    case DEVFREQ_GOV_STOP:
        devfreq_monitor_stop(devfreq);
        break;

    case DEVFREQ_GOV_INTERVAL:
        devfreq_interval_update(devfreq, (unsigned int *)data);
        break;

    case DEVFREQ_GOV_SUSPEND:
        devfreq_monitor_suspend(devfreq);
        break;

    case DEVFREQ_GOV_RESUME:
        devfreq_monitor_resume(devfreq);
        break;

    default:
        break;
    }

    return 0;
}

void devfreq_monitor_start(struct devfreq *devfreq)
{
    /*開始提交“內核工作線程”監控負載狀態*/
    INIT_DEFERRABLE_WORK(&devfreq->work, devfreq_monitor);
    if (devfreq->profile->polling_ms) {
        queue_delayed_work(devfreq_wq, &devfreq->work, msecs_to_jiffies(devfreq->profile->polling_ms));
    }
}

 

(2) 負載監控程序。

a.調用 governor 的 get_target_freq 方法,獲取下一次的調頻目標值。

b.調用OPP device注冊到 devfreq framework 的target函數,設置新的頻率信息。

c.調度延遲工作隊列,延遲OPP device 設置的輪詢間隔后,再次運行。

int update_devfreq(struct devfreq *devfreq)
{
    /*獲取要調頻到的結果頻率*/
    devfreq->governor->get_target_freq(devfreq, &freq);

    /*在調頻前后都有通知發出來*/
    devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE);
    /*調用OPP devices的target函數設置目標頻率*/
    devfreq->profile->target(devfreq->dev.parent, &freq, flags);
    devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
}

static void devfreq_monitor(struct work_struct *work)
{

    err = update_devfreq(devfreq);

    /*進行以polling_ms為周期的周期監控*/
    queue_delayed_work(devfreq_wq, &devfreq->work, msecs_to_jiffies(devfreq->profile->polling_ms));
}

 

(3) governor 的 get_target_freq 方法,調用opp device注冊到devfreq framework的回調函數,獲取當前device負載信息,根據算法,返回調整頻率。

static inline int devfreq_update_stats(struct devfreq *df)
{
    if (df->profile->get_dev_status)
        /*調用OPP device注冊到devfreq framework的回調函數,獲取負載狀態*/
        return df->profile->get_dev_status(df->dev.parent, &df->last_status);
    else
        return -ENODEV;
}

static int devfreq_simple_ondemand_func(struct devfreq *df, unsigned long *freq) /*.get_target_freq = devfreq_simple_ondemand_func*/
{
    /*獲取設備當前狀態*/
    err = devfreq_update_stats(df);
    
    stat = &df->last_status;

    /*然后使用stat與df->data指定的閾值比較,進行目標頻率的設定*/
    if (data && data->simple_scaling) { 
        if (stat->busy_time * 100 > stat->total_time * dfso_upthreshold) /*如果忙的時間大於70%,就給最大頻率*/
            *freq = max;
        else if (stat->busy_time * 100 < stat->total_time * (dfso_upthreshold - dfso_downdifferential)) /*如果忙的時間小於5%,就給最小頻率*/
            *freq = min;
        else
            *freq = df->previous_freq; /*否則還是之前頻率*/
        return 0;
    }

}

 

3. 刪除devfreq設備

(1) 直接調用device_unregister函數注銷devfreq設備。

int devfreq_remove_device(struct devfreq *devfreq)
{
    ...
    device_unregister(&devfreq->dev);
    ...
}

(2) device_unregister 注銷過程中會調用 devfreq_dev_release 函數,完成下面的事務。

a.發送 DEVFREQ_GOV_STOP event,governor停止運行;

b.回調OPP device注冊到devfreq framework的exit函數;

c.釋放devfreq device申請的資源。

static void devfreq_dev_release(struct device *dev) //devfreq_add_device()中初始化賦值的
{
    /*如果governor存在,發送DEVFREQ_GOV_STOP事件,停止governor運行*/
    if (devfreq->governor)
        devfreq->governor->event_handler(devfreq,  DEVFREQ_GOV_STOP, NULL);

    /*如果OPP device注冊的有exit(),調用它*/
    if (devfreq->profile->exit)
        devfreq->profile->exit(devfreq->dev.parent);
}

 

四、架構設計

1.devfreq framework將多種OPP device和多種governor進行抽象,對OPP device提供了統一的接口,來滿足其對devfreq的需求。對governor提供統一的實現格式,為后續擴展不同governor,提供很好的架構支持。是一個典型的子系統實現模式。為后續在我們自己做子系統架構設計,提供了一個很好的參考。

 

2. 用戶空間文件節點

devfreq framework不僅為Kernel空間的設備驅動提供了標准化接口,也為user空間提供了標准化的文件節點,方便user空間的程序,更好的監控和修改。相關文件節點介紹內核文檔:kernel/Documentation/ABI/testing/sysfs-class-devfreq(-event)。

/sys/class/devfreq/xxx.ufshc # ls -l
-r--r--r-- 1 root root 4096 2020-06-07 18:15 available_frequencies
-r--r--r-- 1 root root 4096 2020-06-07 18:15 available_governors
-r--r--r-- 1 root root 4096 2020-06-07 18:15 cur_freq
lrwxrwxrwx 1 root root    0 2020-06-01 10:57 device -> ../../../xxx.ufshc
-rw-r--r-- 1 root root 4096 2020-06-07 18:15 governor
-rw-rw-r-- 1 root root 4096 2020-06-07 18:15 max_freq
-rw-rw-r-- 1 root root 4096 2020-06-07 18:15 min_freq
-rw-r--r-- 1 root root 4096 2020-06-07 18:15 polling_interval
drwxr-xr-x 2 root root    0 2020-06-07 18:15 power
lrwxrwxrwx 1 root root    0 2020-06-01 10:57 subsystem -> ../../../../../../class/devfreq
-r--r--r-- 1 root root 4096 2020-06-07 18:15 target_freq
-r--r--r-- 1 root root 4096 2020-06-07 18:15 trans_stat
-rw-r--r-- 1 root root 4096 1970-02-25 01:42 uevent

available_frequencies: 可用的頻率列表

available_governors:可用的governor

cur_freq:當前頻率

governor: 當前governor

max_freq:最大頻率

min_freq :最小頻率

polling_interval:governor調度的時間間隔,單位是ms.

target_freq:目標頻率

trans_stat:狀態調整表記錄

代碼實現位置:kernel/drivers/devfreq/devfreq.c //struct attribute *devfreq_attrs[]

 

五、調試筆記和總結

1.可以cat trans_stat來查看頻率轉移表。

/sys/class/devfreq/xxx.ufshc # cat trans_stat
     From  :   To
           :  37500000 300000000   time(ms)
*  37500000:         0       133   3343228
  300000000:       133         0    162660
Total transition : 266

2.TODO: 解釋每一個governor的算法
/sys/class/devfreq/xxx.ufshc # cat available_governors
cdspl3 compute mem_latency bw_hwmon vidc-ar50-llcc vidc-ar50-ddr msm-vidc-llcc msm-vidc-ddr gpubw_mon bw_vbif msm-adreno-tz userspace powersave performance simple_ondemand

 

 

參考: https://blog.csdn.net/feelabclihu/article/details/105592301


免責聲明!

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



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