Linux 中断核心模块(rk3399 平台适配)

        核心结论:以下代码均适配 rk3399 平台(ARM64 架构),基于 Linux 4.4+/5.4 + 内核,包含顶半部中断、timer、tasklet、workqueue 四大核心模块,每个模块提供 “API 汇总 + 适用场景 + 可直接编译运行的代码片段”,兼顾实用性和可移植性。

一、通用准备:rk3399 平台适配说明

  1. GPIO 编号规则:rk3399 的 GPIO 分 bank(A~I),每个 bank 有 8 个引脚,编号公式为 “bank 号 ×32 + 引脚号”(如 GPIO0_A5 对应编号 5,GPIO1_B3 对应编号 32+11=43,与用户代码一致)。
  2. 编译依赖:需配置 rk3399 交叉编译工具链(如 aarch64-linux-gnu-gcc),Makefile 需指定内核源码路径。
  3. 运行方式:编译生成.ko 文件后,通过insmod加载,dmesg查看日志,rmmod卸载。

二、模块 1:顶半部中断(rk3399 按键中断示例)

1. 核心 API 汇总

API 函数功能描述参数说明
gpio_to_irq(unsigned gpio)将 GPIO 编号转换为中断编号(IRQ)gpio:rk3399 GPIO 编号(如 5=GPIO0_A5)
request_irq(irq, handler, flags, name, dev_id)注册中断irq:中断编号;handler:中断服务函数;flags:触发方式;name:设备名;dev_id:设备标识(共享中断必填)
free_irq(irq, dev_id)注销中断与 request_irq 的 irq、dev_id 完全一致
gpio_get_value(unsigned gpio)读取 GPIO 电平gpio:GPIO 编号(0 = 低电平,1 = 高电平)

2. 适用场景

  • 外设异步触发需求(按键、传感器、串口接收中断);
  • rk3399 平台常用场景:板载按键、触摸芯片中断、红外接收中断等。

3. rk3399 可运行代码片段(按键中断:GPIO0_A5 = 按键 K1)

c

运行

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/delay.h>

// rk3399按键配置:GPIO0_A5(编号5),双边沿触发,共享中断
#define KEY_GPIO 5
#define KEY_NAME "rk3399-key1"
static int key_irq;
static char key_state = '0'; // 0=松开,1=按下

// 顶半部中断服务函数(ISR)
irqreturn_t key_irq_handler(int irq, void *dev_id) {
    // 快速读取电平(仅做必要操作,不耗时)
    key_state = !gpio_get_value(KEY_GPIO) ? '1' : '0'; // 按键低电平按下
    printk("[rk3399-key] 顶半部:按键状态=%c\n", key_state);
    return IRQ_HANDLED;
}

// 文件操作:应用层读取按键状态
static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *off) {
    if (copy_to_user(buf, &key_state, 1)) {
        return -EFAULT;
    }
    return 1;
}

static const struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .read = key_read,
};

static struct miscdevice key_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = KEY_NAME,
    .fops = &key_fops,
};

// 模块初始化
static int __init rk3399_key_init(void) {
    int ret;
    // 1. 申请GPIO
    ret = gpio_request(KEY_GPIO, KEY_NAME);
    if (ret < 0) {
        printk("[rk3399-key] GPIO申请失败\n");
        return ret;
    }
    // 2. 转换中断编号
    key_irq = gpio_to_irq(KEY_GPIO);
    printk("[rk3399-key] 中断编号=%d\n", key_irq);
    // 3. 注册中断:双边沿触发(按下+松开),共享中断
    ret = request_irq(key_irq, key_irq_handler, 
                     IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
                     KEY_NAME, &key_misc);
    if (ret < 0) {
        printk("[rk3399-key] 中断注册失败\n");
        gpio_free(KEY_GPIO);
        return ret;
    }
    // 4. 注册杂项设备(应用层访问/dev/rk3399-key1)
    ret = misc_register(&key_misc);
    if (ret < 0) {
        printk("[rk3399-key] 设备注册失败\n");
        free_irq(key_irq, &key_misc);
        gpio_free(KEY_GPIO);
        return ret;
    }
    printk("[rk3399-key] 模块加载成功\n");
    return 0;
}

// 模块卸载
static void __exit rk3399_key_exit(void) {
    misc_deregister(&key_misc);
    free_irq(key_irq, &key_misc);
    gpio_free(KEY_GPIO);
    printk("[rk3399-key] 模块卸载成功\n");
}

module_init(rk3399_key_init);
module_exit(rk3399_key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 按键中断顶半部示例");

三、模块 2:内核定时器(rk3399 按键消抖示例)

1. 核心 API 汇总

API 函数功能描述参数说明
setup_timer(timer, func, data)动态初始化定时器timer:定时器对象;func:超时处理函数;data:传递给 func 的参数
add_timer(struct timer_list *timer)启动定时器timer:定时器对象(需先设置 expires)
mod_timer(timer, jiffies + delay)修改定时器超时时间(未启动则启动)delay:延时(单位:jiffies,如 msecs_to_jiffies (20) 表示 20ms)
del_timer_sync(timer)注销定时器(等待执行完成)timer:定时器对象
msecs_to_jiffies(unsigned int m)毫秒转换为 jiffies(内核时间单位)m:毫秒数

2. 适用场景

  • 中断消抖(按键、触摸传感器);
  • 周期性任务(如每隔 1 秒读取传感器数据);
  • rk3399 平台常用场景:按键消抖、LED 呼吸灯、周期性数据采集。

3. rk3399 可运行代码片段(按键消抖:20ms 延时)

c

运行

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

#define KEY_GPIO 5
#define KEY_NAME "rk3399-key-debounce"
#define DEBOUNCE_MS 20 // 消抖延时20ms
static int key_irq;
static char key_state = '0';
static struct timer_list debounce_timer; // 消抖定时器

// 定时器超时处理函数(底半部逻辑)
void debounce_timer_func(unsigned long data) {
    // 消抖后读取真实电平
    key_state = !gpio_get_value(KEY_GPIO) ? '1' : '0';
    printk("[rk3399-debounce] 定时器底半部:按键真实状态=%c\n", key_state);
}

// 顶半部中断服务函数
irqreturn_t key_irq_handler(int irq, void *dev_id) {
    // 启动/重置消抖定时器(20ms后执行)
    mod_timer(&debounce_timer, jiffies + msecs_to_jiffies(DEBOUNCE_MS));
    return IRQ_HANDLED;
}

// 应用层读取接口
static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *off) {
    if (copy_to_user(buf, &key_state, 1)) {
        return -EFAULT;
    }
    return 1;
}

static const struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .read = key_read,
};

static struct miscdevice key_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = KEY_NAME,
    .fops = &key_fops,
};

static int __init rk3399_key_debounce_init(void) {
    int ret;
    // 1. 申请GPIO
    ret = gpio_request(KEY_GPIO, KEY_NAME);
    if (ret < 0) return ret;
    // 2. 转换中断编号
    key_irq = gpio_to_irq(KEY_GPIO);
    // 3. 初始化定时器
    setup_timer(&debounce_timer, debounce_timer_func, (unsigned long)"key-debounce");
    // 4. 注册中断
    ret = request_irq(key_irq, key_irq_handler,
                     IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
                     KEY_NAME, &key_misc);
    if (ret < 0) {
        gpio_free(KEY_GPIO);
        return ret;
    }
    // 5. 注册设备
    ret = misc_register(&key_misc);
    if (ret < 0) {
        free_irq(key_irq, &key_misc);
        gpio_free(KEY_GPIO);
        return ret;
    }
    printk("[rk3399-debounce] 模块加载成功\n");
    return 0;
}

static void __exit rk3399_key_debounce_exit(void) {
    del_timer_sync(&debounce_timer); // 注销定时器
    misc_deregister(&key_misc);
    free_irq(key_irq, &key_misc);
    gpio_free(KEY_GPIO);
    printk("[rk3399-debounce] 模块卸载成功\n");
}

module_init(rk3399_key_debounce_init);
module_exit(rk3399_key_debounce_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 按键消抖(中断+定时器)示例");

四、模块 3:底半部 - tasklet(rk3399 中断后数据处理)

1. 核心 API 汇总

API 函数功能描述参数说明
DECLARE_TASKLET(name, func, data)静态初始化 tasklet(激活状态)name:tasklet 对象;func:处理函数;data:传递参数
tasklet_init(t, func, data)动态初始化 tasklett:tasklet 对象指针;其余同 DECLARE_TASKLET
tasklet_schedule(t)调度 tasklet(加入内核执行链表)t:tasklet 对象指针
tasklet_kill(t)注销 tasklet(等待执行完成)t:tasklet 对象指针

2. 适用场景

  • 中断后快速数据处理(无需睡眠);
  • 比 workqueue 开销小,适合轻量级底半部任务;
  • rk3399 平台常用场景:中断后数据解析、状态上报、简单滤波。

3. rk3399 可运行代码片段(中断 + tasklet 处理)

c

运行

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>

#define KEY_GPIO 5
#define KEY_NAME "rk3399-key-tasklet"
static int key_irq;
static char key_state = '0';

// 1. 定义tasklet处理函数(底半部)
void key_tasklet_func(unsigned long data) {
    // 处理耗时操作(如打印详细日志、简单数据处理)
    printk("[rk3399-tasklet] 底半部:data=%s,按键状态=%c\n", (char*)data, key_state);
}

// 2. 静态初始化tasklet
DECLARE_TASKLET(key_tasklet, key_tasklet_func, (unsigned long)"tasklet-data");

// 3. 顶半部中断服务函数
irqreturn_t key_irq_handler(int irq, void *dev_id) {
    key_state = !gpio_get_value(KEY_GPIO) ? '1' : '0';
    printk("[rk3399-tasklet] 顶半部:触发中断,调度tasklet\n");
    tasklet_schedule(&key_tasklet); // 调度底半部
    return IRQ_HANDLED;
}

// 应用层读取接口
static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *off) {
    if (copy_to_user(buf, &key_state, 1)) return -EFAULT;
    return 1;
}

static const struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .read = key_read,
};

static struct miscdevice key_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = KEY_NAME,
    .fops = &key_fops,
};

static int __init rk3399_tasklet_init(void) {
    int ret;
    ret = gpio_request(KEY_GPIO, KEY_NAME);
    if (ret < 0) return ret;
    key_irq = gpio_to_irq(KEY_GPIO);
    ret = request_irq(key_irq, key_irq_handler,
                     IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
                     KEY_NAME, &key_misc);
    if (ret < 0) {
        gpio_free(KEY_GPIO);
        return ret;
    }
    ret = misc_register(&key_misc);
    if (ret < 0) {
        free_irq(key_irq, &key_misc);
        gpio_free(KEY_GPIO);
        return ret;
    }
    printk("[rk3399-tasklet] 模块加载成功\n");
    return 0;
}

static void __exit rk3399_tasklet_exit(void) {
    tasklet_kill(&key_tasklet); // 注销tasklet
    misc_deregister(&key_misc);
    free_irq(key_irq, &key_misc);
    gpio_free(KEY_GPIO);
    printk("[rk3399-tasklet] 模块卸载成功\n");
}

module_init(rk3399_tasklet_init);
module_exit(rk3399_tasklet_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 中断底半部-tasklet示例");

五、模块 4:底半部 - workqueue(rk3399 耗时任务处理)

1. 核心 API 汇总(共享队列 + 私有队列 + delayed_work)

类型API 函数功能描述参数说明
普通 work(共享队列)INIT_WORK(work, func)动态初始化 work 对象work:work 对象;func:处理函数
schedule_work(work)调度 work 到内核共享队列work:work 对象指针
cancel_work_sync(work)注销 workwork:work 对象指针
私有队列create_workqueue(name)创建私有工作队列name:队列名称,返回队列指针
queue_work(wq, work)将 work 加入私有队列wq:私有队列指针;work:work 对象
destroy_workqueue(wq)销毁私有队列wq:私有队列指针
延迟 workINIT_DELAYED_WORK(dwork, func)初始化延迟 work(带定时器)dwork:delayed_work 对象;func:处理函数
schedule_delayed_work(dwork, delay)延迟调度 work(共享队列)delay:延时(msecs_to_jiffies 转换)
cancel_delayed_work_sync(dwork)注销延迟 workdwork:delayed_work 对象指针

2. 适用场景

队列类型适用场景rk3399 平台示例
共享队列非实时、不耗时的普通任务(省资源)日志打印、状态上报
私有队列实时性要求高、避免阻塞的任务传感器数据读取、外设控制
延迟 work延迟执行的耗时任务中断后延迟 3 秒保存数据、定时清理缓存

3. rk3399 可运行代码片段(3 种 workqueue 示例)

(1)共享队列 - work(基础示例)

c

运行

#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/miscdevice.h>

#define KEY_GPIO 5
#define KEY_NAME "rk3399-key-work"
static int key_irq;
static char key_state = '0';

// 1. 定义work处理函数(进程上下文,可睡眠)
void key_work_func(struct work_struct *work) {
    // 耗时任务:模拟延迟(workqueue支持msleep)
    msleep(100); 
    printk("[rk3399-work] 共享队列:按键状态=%c,耗时处理完成\n", key_state);
}

// 2. 定义并初始化work对象
static DECLARE_WORK(key_work, key_work_func);

// 3. 顶半部中断服务函数
irqreturn_t key_irq_handler(int irq, void *dev_id) {
    key_state = !gpio_get_value(KEY_GPIO) ? '1' : '0';
    printk("[rk3399-work] 顶半部:触发中断,调度共享work\n");
    schedule_work(&key_work); // 加入内核共享队列
    return IRQ_HANDLED;
}

static const struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .read = key_read, // 复用之前的read函数
};

static struct miscdevice key_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = KEY_NAME,
    .fops = &key_fops,
};

static int __init rk3399_work_init(void) {
    int ret;
    ret = gpio_request(KEY_GPIO, KEY_NAME);
    if (ret < 0) return ret;
    key_irq = gpio_to_irq(KEY_GPIO);
    ret = request_irq(key_irq, key_irq_handler,
                     IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
                     KEY_NAME, &key_misc);
    if (ret < 0) {
        gpio_free(KEY_GPIO);
        return ret;
    }
    ret = misc_register(&key_misc);
    if (ret < 0) {
        free_irq(key_irq, &key_misc);
        gpio_free(KEY_GPIO);
        return ret;
    }
    printk("[rk3399-work] 共享队列模块加载成功\n");
    return 0;
}

static void __exit rk3399_work_exit(void) {
    cancel_work_sync(&key_work); // 注销work
    misc_deregister(&key_misc);
    free_irq(key_irq, &key_misc);
    gpio_free(KEY_GPIO);
    printk("[rk3399-work] 模块卸载成功\n");
}

module_init(rk3399_work_init);
module_exit(rk3399_work_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 工作队列-共享队列示例");
(2)私有队列 - work(实时性要求高)

c

运行

#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>

#define KEY_GPIO 5
#define KEY_NAME "rk3399-key-private-work"
#define WQ_NAME "rk3399-private-wq"
static int key_irq;
static struct workqueue_struct *private_wq; // 私有队列
static struct work_struct key_work;

// work处理函数
void key_private_work_func(struct work_struct *work) {
    char state = !gpio_get_value(KEY_GPIO) ? '1' : '0';
    printk("[rk3399-private-wq] 私有队列:按键状态=%c\n", state);
}

// 顶半部中断处理
irqreturn_t key_irq_handler(int irq, void *dev_id) {
    printk("[rk3399-private-wq] 顶半部:触发中断,加入私有队列\n");
    queue_work(private_wq, &key_work); // 加入私有队列
    return IRQ_HANDLED;
}

static int __init rk3399_private_wq_init(void) {
    int ret;
    // 1. 创建私有工作队列
    private_wq = create_workqueue(WQ_NAME);
    if (!private_wq) {
        printk("[rk3399-private-wq] 队列创建失败\n");
        return -ENOMEM;
    }
    // 2. 初始化work对象
    INIT_WORK(&key_work, key_private_work_func);
    // 3. 申请GPIO和中断
    ret = gpio_request(KEY_GPIO, KEY_NAME);
    if (ret < 0) return ret;
    key_irq = gpio_to_irq(KEY_GPIO);
    ret = request_irq(key_irq, key_irq_handler,
                     IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
                     KEY_NAME, private_wq);
    if (ret < 0) {
        gpio_free(KEY_GPIO);
        destroy_workqueue(private_wq);
        return ret;
    }
    printk("[rk3399-private-wq] 私有队列模块加载成功\n");
    return 0;
}

static void __exit rk3399_private_wq_exit(void) {
    cancel_work_sync(&key_work);
    destroy_workqueue(private_wq); // 销毁私有队列
    free_irq(key_irq, private_wq);
    gpio_free(KEY_GPIO);
    printk("[rk3399-private-wq] 模块卸载成功\n");
}

module_init(rk3399_private_wq_init);
module_exit(rk3399_private_wq_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 工作队列-私有队列示例");
(3)延迟 work(delayed_work)

c

运行

#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>

#define DELAY_MS 3000 // 延迟3秒执行
static struct delayed_work delay_work;

// 延迟work处理函数
void delay_work_func(struct work_struct *work) {
    printk("[rk3399-delay-work] 延迟3秒后执行:工作完成\n");
}

static int __init rk3399_delay_work_init(void) {
    // 初始化延迟work
    INIT_DELAYED_WORK(&delay_work, delay_work_func);
    // 调度延迟work(3秒后执行)
    schedule_delayed_work(&delay_work, msecs_to_jiffies(DELAY_MS));
    printk("[rk3399-delay-work] 模块加载成功,延迟3秒执行任务\n");
    return 0;
}

static void __exit rk3399_delay_work_exit(void) {
    cancel_delayed_work_sync(&delay_work); // 注销延迟work
    printk("[rk3399-delay-work] 模块卸载成功\n");
}

module_init(rk3399_delay_work_init);
module_exit(rk3399_delay_work_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("rk3399 工作队列-延迟work示例");

六、rk3399 平台编译与运行脚本

1. Makefile(通用模板,需修改内核路径)

makefile

obj-m += rk3399_key.o rk3399_key_debounce.o rk3399_tasklet.o rk3399_work.o rk3399_private_wq.o rk3399_delay_work.o
KERNELDIR ?= /home/user/rk3399-linux-5.4.y # 替换为你的rk3399内核源码路径
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
clean:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

2. 运行步骤

bash

运行

# 1. 编译(需安装aarch64交叉编译器)
make

# 2. 拷贝.ko文件到rk3399开发板(如通过scp)
scp *.ko root@rk3399-ip:/root/

# 3. 开发板上加载模块(以按键消抖为例)
insmod rk3399_key_debounce.ko

# 4. 查看日志
dmesg | grep rk3399

# 5. 应用层读取按键状态
cat /dev/rk3399-key-debounce

# 6. 卸载模块
rmmod rk3399_key_debounce

      这就是本文的全部内容,也感谢耐心看到这里的读者,我会持续更新,希望你能够多多关注,如果本文有帮组到你的话,还请三连加关注,你的支持就是我创作的最大动力!希望这份手册能帮你少走弯路,欢迎在评论区补充你的调试经验!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值