前言
中断是嵌入式系统中最重要的机制之一,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 中断不触发
排查步骤:
-
检查设备树配置是否正确
-
确认GPIO引脚没有被其他设备占用
-
检查中断触发方式是否匹配硬件信号
-
查看/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月