3. 实现GPU电源管理

实现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 = <&reg_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

运行验证:

  1. 加载模块:insmod gpu_pm.ko,观察dmesg出现“Mali GPU power management initialized”;
  2. 检查运行时PM状态:cat /sys/devices/platform/gpu-pm/power/runtime_status,应显示active;
  3. 触发空闲挂起:停止GPU负载后等待autosuspend延迟(需在probe中设置pm_runtime_set_autosuspend_delay),再次查看状态变为suspended;
  4. DVFS验证:向/sys/kernel/debug/gpu_pm/load写入模拟负载(需额外实现debugfs接口),观察cur_freq节点频率变化。

判定标准:模块加载无错误;runtime_status能在active/suspended间正确切换;DVFS频率随负载阈值(80%/30%)正确升降;断电后重新唤醒GPU功能正常。

GPU电源管理状态机与DVFS调频流程 RPM_ACTIVE clk_on / reg_on RPM_SUSPENDED clk_off / reg_off autosuspend (idle>delay) get_sync (GPU request) LEVEL 0 400MHz / 850mV LEVEL 1 600MHz / 950mV LEVEL 2 800MHz / 1050mV load>80% load>80% load<30% busy/total 采样周期100ms 负载计算 调频决策 >80% 升频 <30% 降频 clk_set_rate reg_set_volt → 状态转换 → DVFS升降频 → 负载采样 → 调频决策 → 执行