【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月

相关推荐
国科安芯15 小时前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao16 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉19 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者20 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间21 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious21 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng1 天前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计1 天前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
KaifuZeng2 天前
通信与接口协议面试一、基本概念
嵌入式硬件·通信与接口协议
wuyk5552 天前
24. C 语言模块化:不是拆几个.c 文件那么简单
c语言·开发语言·stm32·单片机