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");

中断触发与验证

本驱动提供两种验证路径:

  1. 硬件触发:当GPU硬件真正产生完成或错误中断时,对应处理函数被调用。完成中断会唤醒等待队列,错误中断会打印内核日志并累加计数。
  2. 软件模拟触发:通过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"

验证方法与判定标准

验证步骤:

  1. 加载模块后,执行dmesg | grep "GPU interrupt",应看到两条中断注册成功的日志,分别显示complete和error中断号。
  2. 软件触发完成中断:
    adb shell "echo 1 > /sys/kernel/debug/gpu_intr_test/trigger_complete"
    观察dmesg中是否有对应处理被调用的痕迹(可在处理函数中加入临时打印验证)。
  3. 软件触发错误中断:
    adb shell "echo 1 > /sys/kernel/debug/gpu_intr_test/trigger_error"
    观察dmesg中应出现GPU error interrupt occurred日志,且错误计数递增。

判定标准:

  • 模块加载无异常,中断号正确解析。
  • 触发错误中断后,dmesg中出现错误日志,且多次触发计数递增。
  • 触发完成中断后,等待队列被唤醒(可在处理函数中增加唤醒后的状态打印验证)。
GPU中断处理验证流程 驱动加载 (probe) • platform_get_irq_byname "gpu_complete" → IRQ 181 "gpu_error" → IRQ 182 • devm_request_irq × 2 中断处理函数 complete_handler: complete(&done) error_handler: atomic_inc + dev_err 验证触发 路径A: 硬件中断 GPU完成/错误 → IRQ 路径B: debugfs写入 echo 1 > trigger_* 注册 触发 观测点与判定 ① dmesg 注册日志 "GPU interrupt handler registered: complete_irq=181, error_irq=182" ② 错误中断日志 "GPU error interrupt occurred, count=N" (N递增) ③ 完成中断唤醒 wait_for_completion_timeout 返回 > 0 ④ 无异常崩溃 多次触发后系统稳定,无OOPS/panic

测试程序(用户态辅助验证)

以下用户态程序用于配合驱动验证完成中断的唤醒功能:

#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

上述输出为基于代码逻辑的预期示例,实际时间戳与计数以运行为准。