【RK3588 Android12】硬件中断IRQ

前言

中断是嵌入式系统中最重要的机制之一,CPU在执行程序过程中,遇到突发事件需要暂停当前程序去处理。本文介绍RK3588 Android12平台下硬件中断的使用方法。


一、中断基础知识

1.1 什么是中断

中断是指CPU在执行程序过程中,出现某种突发事件需立即处理,CPU必须暂停当前程序,转去处理突发事件,处理完毕后返回原程序继续执行。

类比:你正在敲代码,手机响了,停下来接电话,通话完毕后继续敲代码。

  • 手机响铃 = 中断请求
  • 停止敲代码 = 中断响应
  • 接电话 = 中断处理

1.2 中断控制器GIC

RK3588使用ARM GICv3中断控制器,主要作用是接收硬件中断信号,分发给对应CPU处理。

GIC将中断源分为四类:

类型 中断号 说明
SGI 0~15 软件中断,用于多核通信
PPI 16~31 私有中断,如定时器
SPI 32~1019 共享中断,如GPIO、UART
LPI - 基于消息的中断

1.3 中断触发方式

标志 说明
IRQF_TRIGGER_NONE 无触发
IRQF_TRIGGER_RISING 上升沿触发
IRQF_TRIGGER_FALLING 下降沿触发
IRQF_TRIGGER_HIGH 高电平触发
IRQF_TRIGGER_LOW 低电平触发

二、中断API函数

2.1 头文件

复制代码
#include <linux/interrupt.h>
#include <linux/of_irq.h>

2.2 核心API

函数 说明
request_irq() 申请中断
request_threaded_irq() 申请线程化中断
free_irq() 释放中断
enable_irq() 使能中断
disable_irq() 禁止中断(等待当前处理完成)
disable_irq_nosync() 禁止中断(立即返回)

2.3 request_irq详解

复制代码
int request_irq(unsigned int irq, 
                irq_handler_t handler, 
                unsigned long flags, 
                const char *name, 
                void *dev)

参数说明:

  • irq:中断号
  • handler:中断处理函数
  • flags:中断标志(触发方式、共享等)
  • name:中断名称(/proc/interrupts可见)
  • dev:传递给处理函数的私有数据

常用flags:

标志 说明
IRQF_SHARED 共享中断
IRQF_ONESHOT 单次中断
IRQF_NO_SUSPEND 休眠时不禁止

2.4 中断处理函数

复制代码
irqreturn_t (*irq_handler_t)(int irq, void *dev_id)

返回值:

  • IRQ_NONE:中断不是来自本设备
  • IRQ_HANDLED:中断已处理
  • IRQ_WAKE_THREAD:唤醒线程处理

三、GPIO中断示例

3.1 设备树配置

方式一:使用irq-gpios属性

复制代码
// kernel-5.10/arch/arm64/boot/dts/rockchip/rk3588-xxx.dtsi

irq_test: irq-test {
    compatible = "rockchip,irq-test";
    status = "okay";
    irq-gpios = <&gpio3 RK_PA3 GPIO_ACTIVE_HIGH>;
};

方式二:使用interrupt属性

复制代码
irq_test: irq-test {
    compatible = "rockchip,irq-test";
    status = "okay";
    interrupt-parent = <&gpio3>;
    interrupts = <RK_PA3 IRQ_TYPE_EDGE_RISING>;
};

3.2 驱动代码

复制代码
/*
 * drivers/misc/rk3588_irq.c
 * RK3588 GPIO中断示例驱动
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/workqueue.h>
#include <linux/slab.h>

struct irq_test_data {
    int gpio;
    int irq;
    struct workqueue_struct *wq;
    struct work_struct work;
};

/* 中断下半部 - 工作队列处理函数 */
static void irq_work_func(struct work_struct *work)
{
    struct irq_test_data *data = container_of(work, struct irq_test_data, work);
    
    pr_info("%s: IRQ bottom half, irq=%d\n", __func__, data->irq);
    
    /* 这里处理耗时操作 */
}

/* 中断上半部 - 中断处理函数 */
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
    struct irq_test_data *data = (struct irq_test_data *)dev_id;
    
    pr_info("%s: IRQ top half, irq=%d\n", __func__, irq);
    
    /* 调度下半部处理 */
    queue_work(data->wq, &data->work);
    
    return IRQ_HANDLED;
}

static int rk3588_irq_probe(struct platform_device *pdev)
{
    struct irq_test_data *data;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    int ret;
    
    data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    
    /* 方式一:从irq-gpios获取中断号 */
#if 0
    data->gpio = of_get_named_gpio(np, "irq-gpios", 0);
    if (!gpio_is_valid(data->gpio)) {
        dev_err(dev, "Invalid GPIO\n");
        return -EINVAL;
    }
    
    ret = devm_gpio_request(dev, data->gpio, "irq-test");
    if (ret < 0) {
        dev_err(dev, "Failed to request GPIO\n");
        return ret;
    }
    
    gpio_direction_input(data->gpio);
    data->irq = gpio_to_irq(data->gpio);
#else
    /* 方式二:从interrupts属性获取中断号 */
    data->irq = of_irq_get(np, 0);
#endif
    
    if (data->irq < 0) {
        dev_err(dev, "Failed to get IRQ\n");
        return data->irq;
    }
    
    /* 创建工作队列用于中断下半部 */
    data->wq = create_singlethread_workqueue("irq-test-wq");
    if (!data->wq)
        return -ENOMEM;
    
    INIT_WORK(&data->work, irq_work_func);
    
    /* 申请中断 */
    ret = request_irq(data->irq, gpio_irq_handler,
                      IRQF_TRIGGER_RISING | IRQF_SHARED,
                      "rk3588-irq-test", data);
    if (ret < 0) {
        dev_err(dev, "Failed to request IRQ: %d\n", ret);
        destroy_workqueue(data->wq);
        return ret;
    }
    
    platform_set_drvdata(pdev, data);
    dev_info(dev, "IRQ driver probed, irq=%d\n", data->irq);
    
    return 0;
}

static int rk3588_irq_remove(struct platform_device *pdev)
{
    struct irq_test_data *data = platform_get_drvdata(pdev);
    
    free_irq(data->irq, data);
    destroy_workqueue(data->wq);
    
    return 0;
}

static const struct of_device_id rk3588_irq_of_match[] = {
    { .compatible = "rockchip,irq-test" },
    { }
};
MODULE_DEVICE_TABLE(of, rk3588_irq_of_match);

static struct platform_driver rk3588_irq_driver = {
    .probe  = rk3588_irq_probe,
    .remove = rk3588_irq_remove,
    .driver = {
        .name = "rk3588_irq",
        .of_match_table = rk3588_irq_of_match,
    },
};

module_platform_driver(rk3588_irq_driver);

MODULE_DESCRIPTION("RK3588 IRQ Driver");
MODULE_LICENSE("GPL");

四、线程化中断

对于高频率中断,推荐使用线程化中断处理。

4.1 request_threaded_irq

复制代码
int request_threaded_irq(unsigned int irq,
                         irq_handler_t handler,      // 硬中断处理
                         irq_handler_t thread_fn,    // 线程处理
                         unsigned long flags,
                         const char *name,
                         void *dev)

4.2 示例代码

复制代码
/* 硬中断处理函数 - 快速返回 */
static irqreturn_t gpio_hard_irq(int irq, void *dev_id)
{
    /* 只做最紧急的处理 */
    return IRQ_WAKE_THREAD;  // 唤醒线程处理
}

/* 线程处理函数 - 可以睡眠 */
static irqreturn_t gpio_thread_fn(int irq, void *dev_id)
{
    struct irq_test_data *data = dev_id;
    
    /* 处理耗时操作,允许睡眠 */
    msleep(10);
    process_data(data);
    
    return IRQ_HANDLED;
}

/* 申请线程化中断 */
ret = request_threaded_irq(data->irq,
                           gpio_hard_irq,
                           gpio_thread_fn,
                           IRQF_TRIGGER_RISING | IRQF_ONESHOT,
                           "rk3588-threaded-irq",
                           data);

五、中断上下半部

5.1 为什么要分上下半部

中断处理原则:快进快出。中断处理函数执行时会禁止其他中断,长时间占用会影响系统响应。

上半部 下半部
中断处理函数 工作队列/tasklet/软中断
不能睡眠 可以睡眠(工作队列)
快速执行 处理耗时操作
硬件相关操作 数据处理

5.2 任务划分原则

放入上半部:

  • 对时间敏感的操作
  • 与硬件相关的操作
  • 不希望被其他中断打断的操作

放入下半部:

  • 数据处理
  • 耗时操作
  • 可以延迟执行的操作

六、调试验证

6.1 查看系统中断信息

复制代码
# 查看所有中断
adb shell cat /proc/interrupts

# 输出格式:
#            CPU0       CPU1       CPU2       CPU3
#  106:          5          0          0          0  GICv3  99 Edge      rk3588-irq-test

各列含义:

  • 第1列:逻辑中断号
  • 第2-5列:各CPU上的中断次数
  • 第6列:中断控制器
  • 第7列:硬件中断号
  • 第8列:触发方式(Edge/Level)
  • 第9列:中断名称

6.2 查看内核日志

复制代码
# 实时查看中断日志
adb shell dmesg | grep -i irq

# 触发GPIO中断后查看
adb shell dmesg | tail -20

6.3 手动触发GPIO中断

复制代码
# 查看GPIO状态
adb shell cat /sys/kernel/debug/gpio

# 如果GPIO配置为输入,可以通过外部信号触发
# 或者使用示波器/逻辑分析仪观察

七、常见问题

7.1 中断不触发

排查步骤

  1. 检查设备树配置是否正确

  2. 确认GPIO引脚没有被其他设备占用

  3. 检查中断触发方式是否匹配硬件信号

  4. 查看/proc/interrupts确认中断是否注册

    检查GPIO是否被占用

    adb shell cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins

7.2 中断风暴

问题:中断频繁触发导致系统卡顿

解决

复制代码
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
    struct irq_test_data *data = dev_id;
    
    /* 禁止中断,防止重入 */
    disable_irq_nosync(irq);
    
    /* 调度下半部处理 */
    queue_work(data->wq, &data->work);
    
    return IRQ_HANDLED;
}

static void irq_work_func(struct work_struct *work)
{
    /* 处理完成后重新使能中断 */
    enable_irq(data->irq);
}

7.3 共享中断处理

复制代码
static irqreturn_t shared_irq_handler(int irq, void *dev_id)
{
    struct my_data *data = dev_id;
    
    /* 检查是否是本设备的中断 */
    if (!is_my_interrupt(data))
        return IRQ_NONE;  // 不是本设备的中断
    
    /* 处理中断 */
    handle_interrupt(data);
    
    return IRQ_HANDLED;
}

八、RK3588 GPIO中断号计算

RK3588有5组GPIO(GPIO0~GPIO4),每组32个引脚。

GPIO中断号计算公式:

复制代码
IRQ = gpio_to_irq(GPIO_BANK * 32 + GPIO_PIN)

示例:

  • GPIO3_A3 = GPIO3 * 32 + 3 = 96 + 3 = 99(硬件中断号)

设备树中断配置:

复制代码
interrupt-parent = <&gpio3>;
interrupts = <RK_PA3 IRQ_TYPE_EDGE_RISING>;
// RK_PA3 = 3

最后更新:2026年1月

相关推荐
happygrilclh2 小时前
数码管驱动(一):ET6226M -数据手册主要点分析
单片机·嵌入式硬件
神一样的老师2 小时前
【RT-Thread Titan Board 开发板】显示SD卡上JPEG图片的EXIF信息
人工智能·单片机·嵌入式硬件
柠檬叶子C2 小时前
解决 Keil MDK 编译报错:error: #5: cannot open source input file “xxx.h“
stm32·单片机·开源
qq_429499572 小时前
STM32 SPI读取写入W25Q64JVSSIQ
stm32·单片机·嵌入式硬件
集和诚JHCTECH2 小时前
精准采摘背后的大脑:BRAV-7135边缘计算解决方案赋能智能农业新时代
人工智能·嵌入式硬件
物理与数学3 小时前
SPI/QSPI/OctoSPI/MICROWIRE串行同步通信总线
嵌入式硬件
项目題供诗3 小时前
51单片机入门(五)
单片机·嵌入式硬件·51单片机
秋深枫叶红3 小时前
嵌入式第五十篇——IMX6ULL时钟树
arm开发·单片机·嵌入式硬件
柠檬叶子C4 小时前
STM32CubeIDE 安装教程 | 2026最新STM32CubeIDE安装教程 | STM32CubeIDE保姆级安装教程
ide·stm32·嵌入式硬件