4. 实现GPU中断处理
任务说明
本案例在SA8155平台上实现GPU中断处理:注册GPU中断处理函数,区分GPU完成中断与错误中断,并提供一个可验证的中断触发测试路径。目标代码运行于Linux内核态,以platform driver形式挂载。
中断资源与设备树绑定
SA8155的GPU(Adreno)中断控制器通过SPI中断上报,本案例使用以下中断资源:
- GPU完成中断:中断号假设为
SPI 181(实际平台需按设备树实际配置),用于通知命令队列执行完成。 - GPU错误中断:中断号假设为
SPI 182,用于上报GPU故障(如超时、非法访问)。
设备树节点示例(供参考,实际以目标平台为准):
gpu_test: gpu-test {
compatible = "qcom,gpu-intr-test";
interrupts = <GIC_SPI 181 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 182 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "gpu_complete", "gpu_error";
};
驱动实现
驱动核心逻辑:在probe中通过platform_get_irq_byname获取两个中断号,分别注册处理函数;complete中断处理函数中仅置位完成标志并唤醒等待队列,error中断处理函数中记录错误状态并触发内核告警。同时提供debugfs接口trigger_complete和trigger_error,用于软件模拟中断触发,便于验证。
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/delay.h>
#define DRV_NAME "gpu-intr-test"
struct gpu_intr_dev {
int irq_complete;
int irq_error;
struct completion complete_done;
atomic_t error_count;
struct dentry *dbg_dir;
};
static irqreturn_t gpu_complete_handler(int irq, void *dev_id)
{
struct gpu_intr_dev *gdev = dev_id;
complete(&gdev->complete_done);
return IRQ_HANDLED;
}
static irqreturn_t gpu_error_handler(int irq, void *dev_id)
{
struct gpu_intr_dev *gdev = dev_id;
atomic_inc(&gdev->error_count);
dev_err(&gdev->pdev->dev, "GPU error interrupt occurred, count=%d\n",
atomic_read(&gdev->error_count));
return IRQ_HANDLED;
}
static int gpu_intr_probe(struct platform_device *pdev)
{
struct gpu_intr_dev *gdev;
int ret;
gdev = devm_kzalloc(&pdev->dev, sizeof(*gdev), GFP_KERNEL);
if (!gdev)
return -ENOMEM;
gdev->pdev = pdev;
init_completion(&gdev->complete_done);
atomic_set(&gdev->error_count, 0);
platform_set_drvdata(pdev, gdev);
gdev->irq_complete = platform_get_irq_byname(pdev, "gpu_complete");
if (gdev->irq_complete < 0) {
dev_err(&pdev->dev, "failed to get complete irq\n");
return gdev->irq_complete;
}
gdev->irq_error = platform_get_irq_byname(pdev, "gpu_error");
if (gdev->irq_error < 0) {
dev_err(&pdev->dev, "failed to get error irq\n");
return gdev->irq_error;
}
ret = devm_request_irq(&pdev->dev, gdev->irq_complete,
gpu_complete_handler, 0,
"gpu_complete", gdev);
if (ret) {
dev_err(&pdev->dev, "request complete irq failed: %d\n", ret);
return ret;
}
ret = devm_request_irq(&pdev->dev, gdev->irq_error,
gpu_error_handler, 0,
"gpu_error", gdev);
if (ret) {
dev_err(&pdev->dev, "request error irq failed: %d\n", ret);
return ret;
}
/* debugfs 接口,用于软件触发中断测试 */
gdev->dbg_dir = debugfs_create_dir("gpu_intr_test", NULL);
if (IS_ERR(gdev->dbg_dir)) {
dev_err(&pdev->dev, "failed to create debugfs dir\n");
return PTR_ERR(gdev->dbg_dir);
}
debugfs_create_u32("trigger_complete", 0200, gdev->dbg_dir, &gdev->irq_complete);
debugfs_create_u32("trigger_error", 0200, gdev->dbg_dir, &gdev->irq_error);
dev_info(&pdev->dev, "GPU interrupt handler registered: complete_irq=%d, error_irq=%d\n",
gdev->irq_complete, gdev->irq_error);
return 0;
}
static int gpu_intr_remove(struct platform_device *pdev)
{
struct gpu_intr_dev *gdev = platform_get_drvdata(pdev);
debugfs_remove_recursive(gdev->dbg_dir);
return 0;
}
static const struct of_device_id gpu_intr_of_match[] = {
{ .compatible = "qcom,gpu-intr-test" },
{ }
};
MODULE_DEVICE_TABLE(of, gpu_intr_of_match);
static struct platform_driver gpu_intr_driver = {
.probe = gpu_intr_probe,
.remove = gpu_intr_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = gpu_intr_of_match,
},
};
module_platform_driver(gpu_intr_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SA8155 GPU interrupt handler test driver");
中断触发与验证
本驱动提供两种验证路径:
- 硬件触发:当GPU硬件真正产生完成或错误中断时,对应处理函数被调用。完成中断会唤醒等待队列,错误中断会打印内核日志并累加计数。
- 软件模拟触发:通过debugfs接口向驱动写入中断号,内核会调用
generic_handle_irq触发对应中断处理流程,用于在没有真实GPU负载时验证处理函数逻辑。
编译与加载
# 在SA8155内核源码树中编译
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=$(pwd)/drivers/gpu/drm/msm modules
# 加载模块
adb root
adb push gpu-intr-test.ko /data/local/tmp/
adb shell insmod /data/local/tmp/gpu-intr-test.ko
# 查看注册日志
adb shell dmesg | grep "GPU interrupt"
验证方法与判定标准
验证步骤:
- 加载模块后,执行
dmesg | grep "GPU interrupt",应看到两条中断注册成功的日志,分别显示complete和error中断号。 - 软件触发完成中断:
观察adb shell "echo 1 > /sys/kernel/debug/gpu_intr_test/trigger_complete"dmesg中是否有对应处理被调用的痕迹(可在处理函数中加入临时打印验证)。 - 软件触发错误中断:
观察adb shell "echo 1 > /sys/kernel/debug/gpu_intr_test/trigger_error"dmesg中应出现GPU error interrupt occurred日志,且错误计数递增。
判定标准:
- 模块加载无异常,中断号正确解析。
- 触发错误中断后,dmesg中出现错误日志,且多次触发计数递增。
- 触发完成中断后,等待队列被唤醒(可在处理函数中增加唤醒后的状态打印验证)。
测试程序(用户态辅助验证)
以下用户态程序用于配合驱动验证完成中断的唤醒功能:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
/* 该程序通过debugfs触发中断,并观察驱动行为 */
int main(int argc, char *argv[])
{
int fd;
char buf[16];
if (argc != 2) {
fprintf(stderr, "Usage: %s <complete|error>\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "complete") == 0) {
fd = open("/sys/kernel/debug/gpu_intr_test/trigger_complete", O_WRONLY);
if (fd < 0) {
perror("open complete trigger");
return 1;
}
write(fd, "1", 1);
close(fd);
printf("Triggered GPU complete interrupt. Check dmesg for handler execution.\n");
} else if (strcmp(argv[1], "error") == 0) {
fd = open("/sys/kernel/debug/gpu_intr_test/trigger_error", O_WRONLY);
if (fd < 0) {
perror("open error trigger");
return 1;
}
write(fd, "1", 1);
close(fd);
printf("Triggered GPU error interrupt. Check dmesg for error log.\n");
} else {
fprintf(stderr, "Unknown trigger type: %s\n", argv[1]);
return 1;
}
return 0;
}
编译与运行:
# 交叉编译用户态测试程序
aarch64-linux-gnu-gcc -o gpu_intr_test gpu_intr_test.c
# 推送并执行
adb push gpu_intr_test /data/local/tmp/
adb shell /data/local/tmp/gpu_intr_test error
adb shell dmesg | tail -5
预期输出(示例):
# 触发error中断后dmesg输出
[ 123.456789] gpu-intr-test: GPU error interrupt occurred, count=1
[ 123.456801] gpu-intr-test: GPU error interrupt occurred, count=2
上述输出为基于代码逻辑的预期示例,实际时间戳与计数以运行为准。