3. 实现GPU电源管理
任务说明
本案例为Mali GPU驱动实现电源管理功能,核心目标包含两部分:DVFS(动态电压频率调整)与运行时电源管理(Runtime PM)。DVFS根据GPU负载动态调整工作频率与电压,运行时电源管理在GPU空闲时自动挂起、需要时唤醒。最终交付一个可在Linux内核环境中独立编译的驱动模块,提供完整的电源管理回调函数与负载采样逻辑。
核心数据结构与接口
// gpu_pm.h
#ifndef __GPU_PM_H__
#define __GPU_PM_H__
#include <linux/device.h>
#include <linux/pm_runtime.h>
#include <linux/pm_opp.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
#include <linux/mutex.h>
#define GPU_PM_MAX_LEVELS 8
struct gpu_pm_device {
struct device *dev;
struct clk *clk;
struct regulator *reg;
struct mutex lock;
/* DVFS 状态 */
unsigned long cur_freq_hz;
unsigned long cur_volt_uv;
unsigned int cur_level;
unsigned int max_level;
/* 负载采样 */
unsigned long busy_cycles;
unsigned long total_cycles;
unsigned int load_pct; /* 0-100 */
unsigned long sample_start_jiffies;
};
/* 对外接口 */
int gpu_pm_init(struct gpu_pm_device *pm, struct device *dev);
void gpu_pm_deinit(struct gpu_pm_device *pm);
void gpu_pm_update_load(struct gpu_pm_device *pm,
unsigned long busy, unsigned long total);
int gpu_pm_set_level(struct gpu_pm_device *pm, unsigned int level);
/* 运行时PM回调 */
int gpu_pm_runtime_suspend(struct device *dev);
int gpu_pm_runtime_resume(struct device *dev);
#endif
DVFS实现
// gpu_pm_dvfs.c
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/pm_opp.h>
#include <linux/of.h>
#include "gpu_pm.h"
/* 从设备树或平台数据获取可用频率表 */
static int gpu_pm_parse_opp_table(struct gpu_pm_device *pm)
{
struct device *dev = pm->dev;
struct dev_pm_opp *opp;
unsigned long freq, volt;
int i = 0;
/* 使用OPP框架遍历可用频率-电压对 */
opp = dev_pm_opp_find_freq_floor(dev, &freq);
if (IS_ERR(opp))
return PTR_ERR(opp);
while (!IS_ERR(opp) && i < GPU_PM_MAX_LEVELS) {
volt = dev_pm_opp_get_voltage(opp);
dev_info(dev, "OPP[%d]: %lu Hz, %lu uV\n", i, freq, volt);
dev_pm_opp_put(opp);
/* 记录到驱动私有表(此处简化为仅打印,实际应存储) */
i++;
freq--;
opp = dev_pm_opp_find_freq_floor(dev, &freq);
}
pm->max_level = i - 1;
return (i > 0) ? 0 : -ENODEV;
}
int gpu_pm_set_level(struct gpu_pm_device *pm, unsigned int level)
{
struct dev_pm_opp *opp;
unsigned long freq, volt;
int ret;
if (level > pm->max_level)
return -EINVAL;
mutex_lock(&pm->lock);
/* 获取目标频率 */
opp = dev_pm_opp_find_freq_ceil(pm->dev, &freq);
if (IS_ERR(opp)) {
ret = PTR_ERR(opp);
goto out;
}
volt = dev_pm_opp_get_voltage(opp);
dev_pm_opp_put(opp);
/* 先调电压再调频率(升频)或先降频再降压(降频) */
if (freq > pm->cur_freq_hz) {
ret = regulator_set_voltage(pm->reg, volt, volt);
if (ret)
goto out;
ret = clk_set_rate(pm->clk, freq);
if (ret)
goto out;
} else {
ret = clk_set_rate(pm->clk, freq);
if (ret)
goto out;
ret = regulator_set_voltage(pm->reg, volt, volt);
if (ret)
goto out;
}
pm->cur_freq_hz = freq;
pm->cur_volt_uv = volt;
pm->cur_level = level;
dev_dbg(pm->dev, "DVFS: level=%u freq=%luHz volt=%luuV\n",
level, freq, volt);
ret = 0;
out:
mutex_unlock(&pm->lock);
return ret;
}
/* 负载采样与调频决策 */
void gpu_pm_update_load(struct gpu_pm_device *pm,
unsigned long busy, unsigned long total)
{
unsigned long now = jiffies;
unsigned int load;
int target_level;
if (total == 0)
return;
load = (busy * 100) / total;
pm->load_pct = load;
/* 每100ms采样一次,避免频繁调频 */
if (time_before(now, pm->sample_start_jiffies + msecs_to_jiffies(100)))
return;
pm->sample_start_jiffies = now;
/* 简单策略:负载>80%升频,<30%降频 */
if (load > 80 && pm->cur_level < pm->max_level)
target_level = pm->cur_level + 1;
else if (load < 30 && pm->cur_level > 0)
target_level = pm->cur_level - 1;
else
return;
gpu_pm_set_level(pm, target_level);
}
运行时电源管理实现
// gpu_pm_runtime.c
#include <linux/pm_runtime.h>
#include "gpu_pm.h"
int gpu_pm_runtime_suspend(struct device *dev)
{
struct gpu_pm_device *pm = dev_get_drvdata(dev);
int ret;
dev_dbg(dev, "Runtime suspend: GPU idle, powering off\n");
/* 关闭GPU时钟 */
clk_disable_unprepare(pm->clk);
/* 关闭电源域(若使用regulator) */
if (pm->reg) {
ret = regulator_disable(pm->reg);
if (ret) {
dev_err(dev, "Failed to disable regulator: %d\n", ret);
clk_prepare_enable(pm->clk);
return ret;
}
}
return 0;
}
int gpu_pm_runtime_resume(struct device *dev)
{
struct gpu_pm_device *pm = dev_get_drvdata(dev);
int ret;
dev_dbg(dev, "Runtime resume: GPU active, powering on\n");
/* 使能电源域 */
if (pm->reg) {
ret = regulator_enable(pm->reg);
if (ret) {
dev_err(dev, "Failed to enable regulator: %d\n", ret);
return ret;
}
}
/* 使能GPU时钟 */
ret = clk_prepare_enable(pm->clk);
if (ret) {
dev_err(dev, "Failed to enable clock: %d\n", ret);
if (pm->reg)
regulator_disable(pm->reg);
return ret;
}
/* 恢复上次DVFS频率 */
if (pm->cur_freq_hz) {
ret = clk_set_rate(pm->clk, pm->cur_freq_hz);
if (ret)
dev_warn(dev, "Failed to restore freq %lu: %d\n",
pm->cur_freq_hz, ret);
}
return 0;
}
驱动初始化与PM框架集成
// gpu_pm_main.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/of_device.h>
#include "gpu_pm.h"
static const struct dev_pm_ops gpu_pm_ops = {
SET_RUNTIME_PM_OPS(gpu_pm_runtime_suspend,
gpu_pm_runtime_resume, NULL)
};
static int gpu_pm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpu_pm_device *pm;
int ret;
pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL);
if (!pm)
return -ENOMEM;
pm->dev = dev;
mutex_init(&pm->lock);
dev_set_drvdata(dev, pm);
/* 获取时钟与电源 */
pm->clk = devm_clk_get(dev, "gpu");
if (IS_ERR(pm->clk)) {
dev_err(dev, "Failed to get GPU clock\n");
return PTR_ERR(pm->clk);
}
pm->reg = devm_regulator_get_optional(dev, "gpu");
if (IS_ERR(pm->reg)) {
if (PTR_ERR(pm->reg) == -ENODEV)
pm->reg = NULL;
else
return PTR_ERR(pm->reg);
}
/* 初始化OPP表 */
ret = dev_pm_opp_of_add_table(dev);
if (ret) {
dev_err(dev, "Failed to add OPP table: %d\n", ret);
return ret;
}
/* 使能运行时PM */
pm_runtime_enable(dev);
/* 默认唤醒GPU */
ret = pm_runtime_get_sync(dev);
if (ret < 0) {
dev_err(dev, "Failed to resume GPU: %d\n", ret);
pm_runtime_disable(dev);
return ret;
}
dev_info(dev, "Mali GPU power management initialized\n");
return 0;
}
static void gpu_pm_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
dev_pm_opp_of_remove_table(dev);
}
static const struct of_device_id gpu_pm_match[] = {
{ .compatible = "arm,mali-gpu-pm" },
{ }
};
MODULE_DEVICE_TABLE(of, gpu_pm_match);
static struct platform_driver gpu_pm_driver = {
.probe = gpu_pm_probe,
.remove = gpu_pm_remove,
.driver = {
.name = "mali_gpu_pm",
.of_match_table = gpu_pm_match,
.pm = &gpu_pm_ops,
},
};
module_platform_driver(gpu_pm_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Mali GPU Power Management with DVFS and Runtime PM");
设备树配置示例
// gpu-pm.dtsi 片段
gpu_pm: gpu-pm@13000000 {
compatible = "arm,mali-gpu-pm";
reg = <0x13000000 0x10000>;
clocks = <&clk_gpu>;
clock-names = "gpu";
regulators = <®_gpu>;
regulator-names = "gpu";
operating-points-v2 = <&gpu_opp_table>;
};
gpu_opp_table: opp-table {
compatible = "operating-points-v2";
opp-400000000 {
opp-hz = /bits/ 64 <400000000>;
opp-microvolt = <850000>;
};
opp-600000000 {
opp-hz = /bits/ 64 <600000000>;
opp-microvolt = <950000>;
};
opp-800000000 {
opp-hz = /bits/ 64 <800000000>;
opp-microvolt = <1050000>;
};
};
验证方法
编译验证:
# 内核源码树内编译
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
M=$(pwd)/drivers/gpu/arm/mali_pm modules
# 或独立模块编译
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
运行验证:
- 加载模块:
insmod gpu_pm.ko,观察dmesg出现“Mali GPU power management initialized”; - 检查运行时PM状态:
cat /sys/devices/platform/gpu-pm/power/runtime_status,应显示active; - 触发空闲挂起:停止GPU负载后等待autosuspend延迟(需在probe中设置
pm_runtime_set_autosuspend_delay),再次查看状态变为suspended; - DVFS验证:向
/sys/kernel/debug/gpu_pm/load写入模拟负载(需额外实现debugfs接口),观察cur_freq节点频率变化。
判定标准:模块加载无错误;runtime_status能在active/suspended间正确切换;DVFS频率随负载阈值(80%/30%)正确升降;断电后重新唤醒GPU功能正常。