1. 初始化Mali GPU设备
任务说明
本案例面向Mali GPU驱动开发,目标是在Linux内核驱动框架下完成Mali GPU设备的初始化流程,具体包括:设备树解析与寄存器物理地址获取、寄存器区域映射到内核虚拟地址空间、GPU中断请求线的注册与处理函数绑定、以及电源管理框架的初始化(包含运行时电源管理与系统休眠唤醒回调)。实现范围限定为初始化阶段的代码路径,不涉及GPU命令提交、内存管理或具体渲染任务。
核心数据结构与初始化流程
初始化工作围绕一个核心设备结构体展开,该结构体保存映射后的寄存器基址、中断号、电源管理状态等关键信息。整体流程遵循Linux平台驱动模型:在probe回调中依次完成资源获取、寄存器映射、中断注册和电源管理初始化;在remove回调中执行逆序释放。设备树中需声明寄存器地址、中断号及电源管理相关属性。
驱动代码实现
以下代码实现了一个完整的Mali GPU平台驱动初始化模块。代码包含设备树匹配表、probe/remove回调、中断处理函数、运行时PM回调以及系统级电源管理回调。所有错误路径均执行资源逆序释放,确保初始化失败时不会遗留资源泄漏。
// mali_gpu_init.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
#include <linux/of.h>
#define MALI_GPU_DRV_NAME "mali-gpu"
#define MALI_GPU_REGS_SIZE 0x1000 // 4KB寄存器空间
struct mali_gpu_priv {
void __iomem *reg_base; // 寄存器映射基址
struct resource *res; // 物理资源
int irq; // 中断号
struct clk *clk; // 时钟(可选)
bool irq_registered; // 中断注册标志
bool reg_mapped; // 映射标志
};
// 中断处理函数(仅记录中断,实际业务后续案例扩展)
static irqreturn_t mali_gpu_irq_handler(int irq, void *dev_id)
{
struct mali_gpu_priv *priv = dev_id;
u32 status;
status = readl(priv->reg_base + 0x04); // 读取中断状态寄存器
pr_info("Mali GPU IRQ triggered, status=0x%08x\n", status);
writel(status, priv->reg_base + 0x04); // 写1清除中断
return IRQ_HANDLED;
}
// 运行时PM回调
static int mali_gpu_runtime_suspend(struct device *dev)
{
struct mali_gpu_priv *priv = dev_get_drvdata(dev);
if (priv->clk)
clk_disable_unprepare(priv->clk);
return 0;
}
static int mali_gpu_runtime_resume(struct device *dev)
{
struct mali_gpu_priv *priv = dev_get_drvdata(dev);
if (priv->clk) {
int ret = clk_prepare_enable(priv->clk);
if (ret)
return ret;
}
return 0;
}
// 系统级休眠回调
static int mali_gpu_suspend(struct device *dev)
{
return pm_runtime_force_suspend(dev);
}
static int mali_gpu_resume(struct device *dev)
{
return pm_runtime_force_resume(dev);
}
static const struct dev_pm_ops mali_gpu_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mali_gpu_suspend, mali_gpu_resume)
SET_RUNTIME_PM_OPS(mali_gpu_runtime_suspend,
mali_gpu_runtime_resume, NULL)
};
// probe回调:设备初始化主流程
static int mali_gpu_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mali_gpu_priv *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
// ① 获取寄存器物理地址资源
priv->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!priv->res) {
dev_err(dev, "failed to get memory resource\n");
return -ENODEV;
}
// ② 占用物理地址区间并映射到虚拟地址
if (!request_mem_region(priv->res->start,
resource_size(priv->res),
MALI_GPU_DRV_NAME)) {
dev_err(dev, "failed to request mem region\n");
return -EBUSY;
}
priv->reg_mapped = true;
priv->reg_base = ioremap(priv->res->start, resource_size(priv->res));
if (!priv->reg_base) {
dev_err(dev, "ioremap failed\n");
ret = -ENOMEM;
goto err_release_mem;
}
// ③ 获取中断号并注册中断处理
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0) {
dev_err(dev, "failed to get IRQ\n");
ret = priv->irq;
goto err_iounmap;
}
ret = request_irq(priv->irq, mali_gpu_irq_handler,
IRQF_TRIGGER_HIGH, MALI_GPU_DRV_NAME, priv);
if (ret) {
dev_err(dev, "failed to request IRQ %d\n", priv->irq);
goto err_iounmap;
}
priv->irq_registered = true;
// ④ 获取时钟(设备树中可选配置)
priv->clk = devm_clk_get(dev, NULL);
if (IS_ERR(priv->clk)) {
if (PTR_ERR(priv->clk) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto err_free_irq;
}
priv->clk = NULL; // 无时钟配置时继续
}
// ⑤ 运行时电源管理初始化
pm_runtime_enable(dev);
ret = pm_runtime_get_sync(dev);
if (ret < 0) {
dev_err(dev, "pm_runtime_get_sync failed: %d\n", ret);
pm_runtime_disable(dev);
goto err_free_irq;
}
// 初始化完成:读取GPU ID寄存器验证映射
dev_info(dev, "Mali GPU initialized, ID=0x%08x\n",
readl(priv->reg_base + 0x00));
pm_runtime_put_sync(dev);
return 0;
err_free_irq:
if (priv->irq_registered)
free_irq(priv->irq, priv);
err_iounmap:
if (priv->reg_base)
iounmap(priv->reg_base);
err_release_mem:
if (priv->reg_mapped)
release_mem_region(priv->res->start, resource_size(priv->res));
return ret;
}
// remove回调:逆序释放资源
static int mali_gpu_remove(struct platform_device *pdev)
{
struct mali_gpu_priv *priv = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
if (priv->irq_registered)
free_irq(priv->irq, priv);
if (priv->reg_base)
iounmap(priv->reg_base);
if (priv->reg_mapped)
release_mem_region(priv->res->start, resource_size(priv->res));
return 0;
}
static const struct of_device_id mali_gpu_of_match[] = {
{ .compatible = "arm,mali-gpu", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mali_gpu_of_match);
static struct platform_driver mali_gpu_driver = {
.probe = mali_gpu_probe,
.remove = mali_gpu_remove,
.driver = {
.name = MALI_GPU_DRV_NAME,
.of_match_table = mali_gpu_of_match,
.pm = &mali_gpu_pm_ops,
},
};
module_platform_driver(mali_gpu_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Mali GPU device initialization driver");
MODULE_VERSION("1.0");
设备树配置示例
驱动依赖设备树提供寄存器地址、中断号和可选时钟。以下节点展示了最小配置,寄存器地址和中断号需根据实际硬件调整。
// 设备树节点示例(arch/arm/boot/dts/your-board.dts)
gpu@13000000 {
compatible = "arm,mali-gpu";
reg = <0x13000000 0x1000>; // 物理地址和大小
interrupts = <0 42 4>; // GIC SPI 42, 高电平触发
clocks = <&gpu_clk>; // 可选
clock-names = "gpu";
status = "okay";
};
编译与加载验证
将驱动代码放入内核源码drivers/gpu/drm/或独立目录,在Kconfig中添加配置项后编译。验证步骤如下:
- 编译驱动:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-,确认生成mali_gpu_init.ko模块文件。 - 加载模块:
insmod mali_gpu_init.ko,观察内核日志出现Mali GPU initialized, ID=0x...信息,其中ID值对应硬件GPU版本号。 - 验证中断:触发GPU事件(如写入特定寄存器),确认日志输出
Mali GPU IRQ triggered。 - 验证电源管理:执行
echo auto > /sys/devices/platform/gpu@13000000/power/control,再读写GPU寄存器触发运行时挂起/恢复,观察无错误日志。 - 卸载模块:
rmmod mali_gpu_init,确认无资源泄漏警告(可通过/proc/iomem和/proc/interrupts对比加载前后状态)。
判定标准:加载时日志显示GPU ID读取成功;中断触发时处理函数被调用;卸载后/proc/iomem中对应区域已释放,/proc/interrupts中中断号不再被驱动占用。若probe过程中任一环节失败,错误路径应释放所有已获取资源,且dmesg中无BUG或WARNING。