裝置能量模型¶
1. 概述¶
能量模型 (EM) 框架充當驅動程式(瞭解裝置在各種效能級別下的功耗)和核心子系統(願意使用該資訊做出節能決策)之間的介面。
關於裝置功耗的資訊來源可能因平臺而異。在某些情況下,可以使用裝置樹資料來估計這些功耗。在其他情況下,韌體會更清楚。或者,使用者空間可能處於最佳位置。等等。為了避免每個客戶端子系統都重新實現對每個可能的資訊來源的支援,EM 框架作為一個抽象層進行干預,該抽象層標準化核心中功耗表的格式,從而能夠避免冗餘工作。
功率值可以用微瓦或“抽象比例”表示。多個子系統可能會使用 EM,並且由系統整合商檢查是否滿足功率值比例型別的要求。可以在節能排程器文件 節能排程 中找到一個示例。對於某些子系統(如散熱或功耗限制),以“抽象比例”表示的功率值可能會導致問題。這些子系統更關心過去功耗的估計,因此可能需要真實的微瓦值。這些要求的示例可以在智慧功率分配中的 功率分配器調控器可調引數 中找到。核心子系統可能會實現自動檢測,以檢查 EM 註冊的裝置是否具有不一致的比例(基於 EM 內部標誌)。要記住的重要一點是,當功率值以“抽象比例”表示時,將無法得出真實的以微焦耳為單位的能量。
下圖描述了驅動程式(此處特定於 Arm,但該方法適用於任何架構)向 EM 框架提供功耗,以及感興趣的客戶端從中讀取資料的示例
+---------------+ +-----------------+ +---------------+
| Thermal (IPA) | | Scheduler (EAS) | | Other |
+---------------+ +-----------------+ +---------------+
| | em_cpu_energy() |
| | em_cpu_get() |
+---------+ | +---------+
| | |
v v v
+---------------------+
| Energy Model |
| Framework |
+---------------------+
^ ^ ^
| | | em_dev_register_perf_domain()
+----------+ | +---------+
| | |
+---------------+ +---------------+ +--------------+
| cpufreq-dt | | arm_scmi | | Other |
+---------------+ +---------------+ +--------------+
^ ^ ^
| | |
+--------------+ +---------------+ +--------------+
| Device Tree | | Firmware | | ? |
+--------------+ +---------------+ +--------------+
對於 CPU 裝置,EM 框架管理系統中每個“效能域”的功耗表。效能域是一組效能一起縮放的 CPU。效能域通常與 CPUFreq 策略具有 1 對 1 的對映。效能域中的所有 CPU 都需要具有相同的微架構。不同效能域中的 CPU 可以具有不同的微架構。
為了更好地反映由於靜態功耗(洩漏)引起的功率變化,EM 支援執行時修改功率值。該機制依賴於 RCU 來釋放可修改的 EM perf_state 表記憶體。其使用者,任務排程器,也使用 RCU 來訪問此記憶體。EM 框架提供 API 用於為可修改的 EM 表分配/釋放新記憶體。當給定的 EM 執行時表例項不再有所有者時,舊記憶體將使用 RCU 回撥機制自動釋放。這是使用 kref 機制跟蹤的。提供新 EM 的裝置驅動程式應在不再需要時呼叫 EM API 以安全地釋放它。EM 框架將在可能的情況下處理清理。
想要修改 EM 值的核心程式碼受到互斥鎖的保護,防止併發訪問。因此,裝置驅動程式程式碼在嘗試修改 EM 時必須在睡眠上下文中執行。
使用執行時可修改的 EM,我們從“單個且在整個執行時靜態 EM”(系統屬性)設計切換到“可以根據例如工作負載更改的單個 EM”(系統和工作負載屬性)設計。
也可以修改每個 EM 效能狀態的 CPU 效能值。因此,可以根據例如工作負載或系統屬性更改完整的功率和效能曲線(這是一個指數曲線)。
2. 核心 API¶
2.1 配置選項¶
必須啟用 CONFIG_ENERGY_MODEL 才能使用 EM 框架。
2.2 註冊效能域¶
註冊“高階”EM¶
“高階”EM 之所以得名,是因為允許驅動程式提供更精確的功率模型。它不限於框架中實現的某些數學公式(就像“簡單”EM 那樣)。它可以更好地反映為每個效能狀態執行的實際功率測量。因此,如果考慮 EM 靜態功率(洩漏)很重要,則應首選此註冊方法。
預計驅動程式會透過呼叫以下 API 將效能域註冊到 EM 框架中
int em_dev_register_perf_domain(struct device *dev, unsigned int nr_states,
struct em_data_callback *cb, cpumask_t *cpus, bool microwatts);
驅動程式必須提供一個回撥函式,該函式為每個效能狀態返回 <frequency, power> 元組。驅動程式提供的回撥函式可以自由地從任何相關位置(DT、韌體等)以及以任何認為必要的方式獲取資料。僅對於 CPU 裝置,驅動程式必須使用 cpumask 指定效能域的 CPU。對於 CPU 以外的其他裝置,最後一個引數必須設定為 NULL。最後一個引數“microwatts”對於設定正確的值很重要。使用 EM 的核心子系統可能會依賴於此標誌來檢查所有 EM 裝置是否使用相同的比例。如果存在不同的比例,這些子系統可能會決定返回警告/錯誤、停止工作或崩潰。有關實現此回撥的驅動程式的示例,請參見第 3 節,有關此 API 的更多文件,請參見第 2.4 節
使用 DT 註冊 EM¶
也可以使用 OPP 框架和 DT “operating-points-v2” 中的資訊註冊 EM。DT 中的每個 OPP 條目都可以使用包含微瓦功率值的屬性“opp-microwatt”進行擴充套件。此 OPP DT 屬性允許平臺註冊反映總功率(靜態 + 動態)的 EM 功率值。這些功率值可能直接來自實驗和測量。
註冊“人工”EM¶
可以選擇為缺少每個效能狀態功率值詳細知識的驅動程式提供自定義回撥。回撥 .get_cost() 是可選的,並提供 EAS 使用的“成本”值。這對於僅提供 CPU 型別之間相對效率資訊的平臺很有用,其中可以使用該資訊來建立抽象功率模型。但是,即使是抽象功率模型,鑑於輸入功率值大小的限制,有時也難以適應。.get_cost() 允許提供反映 CPU 效率的“成本”值。這將允許提供與 EM 內部公式計算的“成本”值具有不同關係的 EAS 資訊。要為此類平臺註冊 EM,驅動程式必須將標誌“microwatts”設定為 0,提供 .get_power() 回撥並提供 .get_cost() 回撥。EM 框架將在註冊期間正確處理此類平臺。為該平臺設定了標誌 EM_PERF_DOMAIN_ARTIFICIAL。其他使用 EM 的框架應特別注意測試和正確處理此標誌。
註冊“簡單”EM¶
使用框架輔助函式 cpufreq_register_em_with_opp() 註冊“簡單”EM。它實現了一個緊密結合數學公式的功率模型
Power = C * V^2 * f
使用此方法註冊的 EM 可能無法正確反映真實裝置的物理特性,例如,當靜態功率(洩漏)很重要時。
2.3 訪問效能域¶
有兩個 API 函式提供對能量模型的訪問:em_cpu_get(),它將 CPU ID 作為引數,以及 em_pd_get(),它將裝置指標作為引數。這取決於子系統將使用哪個介面,但在 CPU 裝置的情況下,這兩個函式都返回相同的效能域。
對 CPU 的能量模型感興趣的子系統可以使用 em_cpu_get() API 檢索它。能量模型表在效能域建立時分配一次,並保持在記憶體中不變。
可以使用 em_cpu_energy() API 估計效能域消耗的能量。假設在 CPU 裝置的情況下使用 schedutil CPUfreq 調控器來執行估計。目前,未為其他型別的裝置提供此計算。
有關上述 API 的更多詳細資訊,請參見 <linux/energy_model.h> 或第 2.5 節
2.4 執行時修改¶
希望在執行時更新 EM 的驅動程式應使用以下專用函式來分配修改後的 EM 的新例項。API 如下所示
struct em_perf_table __rcu *em_table_alloc(struct em_perf_domain *pd);
這允許分配一個結構,該結構包含新 EM 表以及 EM 框架所需的 RCU 和 kref。“struct em_perf_table” 包含陣列 “struct em_perf_state state[]”,這是一個按升序排列的效能狀態列表。該列表必須由想要更新 EM 的裝置驅動程式填充。頻率列表可以從現有 EM(在啟動期間建立)中獲取。驅動程式也必須填充 “struct em_perf_state” 中的內容。
這是使用 RCU 指標交換執行 EM 更新的 API
int em_dev_update_perf_domain(struct device *dev,
struct em_perf_table __rcu *new_table);
驅動程式必須提供指向已分配和初始化的新 EM “struct em_perf_table” 的指標。該新 EM 將在 EM 框架內部安全使用,並且對核心(散熱,功耗限制)中的其他子系統可見。此 API 的主要設計目標是快速並避免在執行時進行額外的計算或記憶體分配。當預先計算的 EM 在裝置驅動程式中可用時,應該可以以較低的效能開銷簡單地重用它們。
為了釋放先前由驅動程式提供的 EM(例如,當模組解除安裝時),需要呼叫 API
void em_table_free(struct em_perf_table __rcu *table);
當沒有其他子系統使用它時,例如 EAS,這將允許 EM 框架安全地刪除記憶體。
為了在其他子系統(如散熱,功耗限制)中使用功率值,需要呼叫保護讀取器並提供 EM 表資料一致性的 API
struct em_perf_state *em_perf_state_from_pd(struct em_perf_domain *pd);
它返回 “struct em_perf_state” 指標,該指標是一個按升序排列的效能狀態陣列。必須在 RCU 讀取鎖定部分(在 rcu_read_lock() 之後)呼叫此函式。當不再需要 EM 表時,需要呼叫 rcu_real_unlock()。透過這種方式,EM 安全地使用 RCU 讀取部分並保護使用者。它還允許 EM 框架管理記憶體並釋放它。有關如何使用它的更多詳細資訊,請參見示例驅動程式中的第 3.2 節。
裝置驅動程式有專用的 API 來計算 em_perf_state::cost 值
int em_dev_compute_costs(struct device *dev, struct em_perf_state *table,
int nr_states);
EM 中的這些 “cost” 值在 EAS 中使用。新的 EM 表應與條目數和裝置指標一起傳遞。當成本值的計算正確完成時,該函式的返回值將為 0。該函式還負責正確設定每個效能狀態的低效率。它相應地更新 em_perf_state::flags。然後可以將這樣準備好的新 EM 傳遞給 em_dev_update_perf_domain() 函式,這將允許使用它。
有關上述 API 的更多詳細資訊,請參見 <linux/energy_model.h> 或第 3.2 節,其中包含一個示例程式碼,顯示了裝置驅動程式中更新機制的簡單實現。
2.5 此 API 的詳細描述¶
-
struct em_perf_state¶
效能域的效能狀態
定義:
struct em_perf_state {
unsigned long performance;
unsigned long frequency;
unsigned long power;
unsigned long cost;
unsigned long flags;
};
成員
performance給定頻率下的 CPU 效能(容量)
frequency以 KHz 為單位的頻率,與 CPUFreq 保持一致
power在此級別消耗的功率(由 1 個 CPU 或由註冊裝置)。它可以是總功率:靜態和動態。
cost與此級別關聯的成本系數,在能量計算期間使用。等於:功率 * 最大頻率 / 頻率
flags請參見下面的 “em_perf_state flags” 說明。
-
struct em_perf_table¶
效能狀態表
定義:
struct em_perf_table {
struct rcu_head rcu;
struct kref kref;
struct em_perf_state state[];
};
成員
rcuRCU 用於安全訪問和銷燬
kref用於跟蹤使用者的引用計數器
state效能狀態列表,按升序排列
-
struct em_perf_domain¶
效能域
定義:
struct em_perf_domain {
struct em_perf_table __rcu *em_table;
int nr_perf_states;
int min_perf_state;
int max_perf_state;
unsigned long flags;
unsigned long cpus[];
};
成員
em_table指向執行時可修改的 em_perf_table 的指標
nr_perf_states效能狀態數
min_perf_state允許的最小效能狀態索引
max_perf_state允許的最大效能狀態索引
flags請參見 “em_perf_domain flags”
cpus覆蓋域的 CPU 的 cpumask。這是出於效能原因,為了避免在排程器中的能量計算期間可能發生的快取未命中,並簡化分配/釋放該記憶體區域。
描述
對於 CPU 裝置,“效能域” 表示一組效能一起縮放的 CPU。效能域的所有 CPU 必須具有相同的微架構。效能域通常與 CPUFreq 策略具有 1 對 1 的對映。對於其他裝置,**cpus** 欄位未使用。
-
int em_pd_get_efficient_state(struct em_perf_state *table, struct em_perf_domain *pd, unsigned long max_util)¶
從 EM 獲取有效的效能狀態
引數
struct em_perf_state *table效能狀態列表,按升序排列
struct em_perf_domain *pd必須為其完成此操作的效能域
unsigned long max_util要與 EM 對映的最大利用率
描述
它經常從排程程式程式碼中呼叫,因此不實現任何檢查。
返回
一個有效的效能狀態 ID,足以滿足 **max_util** 要求。
-
unsigned long em_cpu_energy(struct em_perf_domain *pd, unsigned long max_util, unsigned long sum_util, unsigned long allowed_cpu_cap)¶
估計效能域的 CPU 消耗的能量
引數
struct em_perf_domain *pd必須為其估計能量的效能域
unsigned long max_util域中 CPU 的最高利用率
unsigned long sum_util域中所有 CPU 的利用率之和
unsigned long allowed_cpu_cap**pd** 的最大允許 CPU 容量,可能反映降低的頻率(由於散熱)
描述
此函式只能用於 CPU 裝置。沒有驗證,即 EM 是否為 CPU 型別並且已分配 cpumask。它經常從排程程式程式碼中呼叫,這就是為什麼沒有檢查的原因。
返回
假設容量狀態滿足域的最大利用率,則域的 CPU 消耗的能量之和。
-
int em_pd_nr_perf_states(struct em_perf_domain *pd)¶
獲取效能域的效能狀態數
引數
struct em_perf_domain *pd必須為其完成此操作的效能域
返回
效能域表中的效能狀態數
-
struct em_perf_state *em_perf_state_from_pd(struct em_perf_domain *pd)¶
獲取效能域的效能狀態表
引數
struct em_perf_domain *pd必須為其完成此操作的效能域
描述
要使用此函式,應保持 rcu_read_lock()。完成效能狀態表的使用後,應呼叫 rcu_read_unlock()。
返回
指向效能域的效能狀態表的指標
-
int em_dev_update_perf_domain(struct device *dev, struct em_perf_table *new_table)¶
更新裝置的執行時 EM 表
引數
struct device *dev要更新 EM 的裝置
struct em_perf_table *new_table從現在開始將使用的新 EM 表
描述
使用提供的 **table** 更新 **dev** 的 EM 執行時可修改表。
此函式使用互斥鎖來序列化寫入器,因此不得從非睡眠上下文中呼叫它。
成功時返回 0,失敗時返回錯誤程式碼。
-
struct em_perf_domain *em_pd_get(struct device *dev)¶
返回裝置的效能域
引數
struct device *dev要查詢效能域的裝置
描述
返回 **dev** 所屬的效能域,如果不存在,則返回 NULL。
-
struct em_perf_domain *em_cpu_get(int cpu)¶
返回 CPU 的效能域
引數
int cpu要查詢效能域的 CPU
描述
返回 **cpu** 所屬的效能域,如果不存在,則返回 NULL。
-
int em_dev_register_perf_domain(struct device *dev, unsigned int nr_states, const struct em_data_callback *cb, const cpumask_t *cpus, bool microwatts)¶
為設備註冊能量模型 (EM)
引數
struct device *dev要註冊 EM 的裝置
unsigned int nr_states要註冊的效能狀態數
const struct em_data_callback *cb提供能量模型資料的回撥函式
const cpumask_t *cpus指向 cpumask_t 的指標,在 CPU 裝置的情況下是必需的。它可以從 i.e. ‘policy->cpus’ 中獲取。對於其他型別的裝置,應將其設定為 NULL。
bool microwatts標誌,指示功率值是否以微瓦或其他比例表示。必須正確設定它。
描述
使用 cb 中定義的回撥函式為效能域建立能量模型表。
正確設定 **microwatts** 非常重要。某些核心子系統可能會依賴此標誌並檢查 EM 中的所有裝置是否使用相同的比例。
如果多個客戶端註冊同一性能域,則除第一個註冊外的所有註冊都將被忽略。
成功時返回 0
引數
struct device *dev註冊 EM 的裝置
描述
登出指定的 **dev** 的 EM(但不是 CPU 裝置)。
引數
struct device *dev必須更新能量模型的裝置。
描述
此函式允許使用 OPP 框架和 DT 中可用的新值輕鬆更新 EM。在裝置驅動程式正確驗證晶片並調整晶片分級的電壓後,可以使用它。
-
int em_update_performance_limits(struct em_perf_domain *pd, unsigned long freq_min_khz, unsigned long freq_max_khz)¶
使用效能限制資訊更新能量模型。
引數
struct em_perf_domain *pd必須更新 EM 的效能域。
unsigned long freq_min_khz此裝置的新最小允許頻率。
unsigned long freq_max_khz此裝置的新最大允許頻率。
描述
此函式允許使用有關可用效能級別的資訊更新 EM。它採用 kHz 為單位的最小和最大頻率,並進行內部轉換到效能級別。成功時返回 0,失敗時返回 -EINVAL。
3. 示例¶
3.1 帶有 EM 註冊的示例驅動程式¶
CPUFreq 框架支援專用回撥,用於為給定的 CPU(多個)“策略”物件註冊 EM:cpufreq_driver::register_em()。必須為給定的驅動程式正確實現該回調,因為框架將在設定期間的正確時間呼叫它。本節提供了一個 CPUFreq 驅動程式的簡單示例,該驅動程式使用(偽)“foo”協議在能量模型框架中註冊效能域。驅動程式實現了一個 est_power() 函式,以提供給 EM 框架
-> drivers/cpufreq/foo_cpufreq.c
01 static int est_power(struct device *dev, unsigned long *mW,
02 unsigned long *KHz)
03 {
04 long freq, power;
05
06 /* Use the 'foo' protocol to ceil the frequency */
07 freq = foo_get_freq_ceil(dev, *KHz);
08 if (freq < 0);
09 return freq;
10
11 /* Estimate the power cost for the dev at the relevant freq. */
12 power = foo_estimate_power(dev, freq);
13 if (power < 0);
14 return power;
15
16 /* Return the values to the EM framework */
17 *mW = power;
18 *KHz = freq;
19
20 return 0;
21 }
22
23 static void foo_cpufreq_register_em(struct cpufreq_policy *policy)
24 {
25 struct em_data_callback em_cb = EM_DATA_CB(est_power);
26 struct device *cpu_dev;
27 int nr_opp;
28
29 cpu_dev = get_cpu_device(cpumask_first(policy->cpus));
30
31 /* Find the number of OPPs for this policy */
32 nr_opp = foo_get_nr_opp(policy);
33
34 /* And register the new performance domain */
35 em_dev_register_perf_domain(cpu_dev, nr_opp, &em_cb, policy->cpus,
36 true);
37 }
38
39 static struct cpufreq_driver foo_cpufreq_driver = {
40 .register_em = foo_cpufreq_register_em,
41 };
3.2 帶有 EM 修改的示例驅動程式¶
本節提供了一個散熱驅動程式的簡單示例,該驅動程式修改 EM。驅動程式實現了一個 foo_thermal_em_update() 函式。定期喚醒驅動程式以檢查溫度並修改 EM 資料
-> drivers/soc/example/example_em_mod.c
01 static void foo_get_new_em(struct foo_context *ctx)
02 {
03 struct em_perf_table __rcu *em_table;
04 struct em_perf_state *table, *new_table;
05 struct device *dev = ctx->dev;
06 struct em_perf_domain *pd;
07 unsigned long freq;
08 int i, ret;
09
10 pd = em_pd_get(dev);
11 if (!pd)
12 return;
13
14 em_table = em_table_alloc(pd);
15 if (!em_table)
16 return;
17
18 new_table = em_table->state;
19
20 rcu_read_lock();
21 table = em_perf_state_from_pd(pd);
22 for (i = 0; i < pd->nr_perf_states; i++) {
23 freq = table[i].frequency;
24 foo_get_power_perf_values(dev, freq, &new_table[i]);
25 }
26 rcu_read_unlock();
27
28 /* Calculate 'cost' values for EAS */
29 ret = em_dev_compute_costs(dev, new_table, pd->nr_perf_states);
30 if (ret) {
31 dev_warn(dev, "EM: compute costs failed %d\n", ret);
32 em_table_free(em_table);
33 return;
34 }
35
36 ret = em_dev_update_perf_domain(dev, em_table);
37 if (ret) {
38 dev_warn(dev, "EM: update failed %d\n", ret);
39 em_table_free(em_table);
40 return;
41 }
42
43 /*
44 * Since it's one-time-update drop the usage counter.
45 * The EM framework will later free the table when needed.
46 */
47 em_table_free(em_table);
48 }
49
50 /*
51 * Function called periodically to check the temperature and
52 * update the EM if needed
53 */
54 static void foo_thermal_em_update(struct foo_context *ctx)
55 {
56 struct device *dev = ctx->dev;
57 int cpu;
58
59 ctx->temperature = foo_get_temp(dev, ctx);
60 if (ctx->temperature < FOO_EM_UPDATE_TEMP_THRESHOLD)
61 return;
62
63 foo_get_new_em(ctx);
64 }