文章目录
一、中断处理过程流程图:
中断控制器(GIC)
**中断原则:**a.不能嵌套
b.处理函数越快越好
**耗时中断的处理:**a.分为上半部和下半部
b.内核线程处理中断
中断上半部:在CPU处理紧急事件时候,不可以被其他中断打断,此时cpu关中断
中断下半部:在CPU处理非紧急事件时候,可以被其他中断打断
所以有时候底半部相应会比较慢,为了解决此问题,引入内核线程(work pthread)、工作队列(work queue),在执行中断上半部时,往队列放入处理函数。
二、中断底半部机制:
1、软中断
Linux 内核使用结构体 softirq_action 表示软中断, softirq_action结构体定义在文件 include/linux/interrupt.h 中,内容如下:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
在 kernel/softirq.c 文件中一共定义了 10 个软中断,如下所示:
static struct softirq_action softirq_vec[NR_SOFTIRQS];
NR_SOFTIRQS 是枚举类型,定义在文件 include/linux/interrupt.h 中,定义如下:
enum
{
HI_SOFTIRQ=0, /* 高优先级软中断 */
TIMER_SOFTIRQ, /* 定时器软中断 */
NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ, /* tasklet 软中断 */
SCHED_SOFTIRQ, /* 调度软中断 */
HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */
RCU_SOFTIRQ, /* RCU 软中断 */
NR_SOFTIRQS
};
NR_SOFTIRQS 为 10
softirq_action 结构体中的 action 成员变量就是软中断的服务函数,数组 softirq_vec 是个全局数组,全局数组softirq_vec 传入action ,因此所有的 CPU(对于 SMP 系统而言)都可以访问到,每个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断。但是各个 CPU 所执行的软中断服务函数确是相同的,都是数组 softirq_vec 中定义的 action 函数。要使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数,open_softirq 函数原型如下:
void open_softirq(int nr, void (*action)(struct softirq_action *))
nr:要开启的软中断,在示例代码 31.1.2.3 中选择要开启的软中断。
action:软中断对应的处理函数
注册好软中断以后需要通过 raise_softirq 函数触发,raise_softirq 函数原型如下:
void raise_softirq(unsigned int nr)
nr:要触发的软中断,在示例代码 31.1.2.3 中选择要注册的软中断。
**返回值:**没有返回值。
软中断必须在编译的时候静态注册!Linux 内核使用 softirq_init 函数初始化软中断,softirq_init 函数定义在 kernel/softirq.c 文件里面,函数内容如下:
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
softirq_init 函数默认会打开 TASKLET_SOFTIRQ 和HI_SOFTIRQ
2、tasklet
tasklet 是利用软中断来实现的另外一种下半部机制
/* 用于创建tasklet变量 */
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
func 函数就是 tasklet 要执行的处理函数,用户实现具体的函数内容,相当于中断处理函数。
① tasklet_init
如果要使用 tasklet,必须先定义一个 tasklet_struct 变量,然后使用 tasklet_init 函数对其进行初始化,taskled_init 函数原型如下:
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
t:要初始化的tasklet,即struct tasklet_struct定义的值
func: tasklet 的处理函数。task_function
**data:**要传递给 func 函数的参数
**返回值:**没有返回值。
也 可 以 使 用 宏 DECLARE_TASKLET 来 一 次 性 完 成 tasklet 的 定 义 和 初 始 化 ,DECLARE_TASKLET 定义在 include/linux/interrupt.h 文件中,定义如下:
DECLARE_TASKLET(name, func, data)
其中 name 为要定义的 tasklet 名字,其实就是 tasklet_struct 类型的变量名,func 就是 tasklet的处理函数,data 是传递给 func 函数的参数。
②tasklet_schedule
在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行,tasklet_schedule 函数原型如下
void tasklet_schedule(struct tasklet_struct *t)
函数参数和返回值含义如下:
t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。
**返回值:**没有返回值。
tasklet框架:
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 , 由tasklet_init传入*/
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/* 中断处理函数 由request_irq传入 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 tasklet 传入tasklet结构体 */
tasklet_schedule(&testtasklet);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 tasklet 传入 tasklet tasklet函数 tasklet函数的参数*/
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
3、工作队列
工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。
Linux 内核使用 work_struct 结构体表示一个工作,内容如下(省略掉条件编译):
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示,内容如下(省略掉条件编译):
struct workqueue_struct {
struct list_head pwqs;
struct list_head list;
struct mutex mutex;
int work_color;
int flush_color;
atomic_t nr_pwqs_to_flush;
struct wq_flusher *first_flusher;
struct list_head flusher_queue;
struct list_head flusher_overflow;
struct list_head maydays;
struct worker *rescuer;
int nr_drainers;
int saved_max_active;
struct workqueue_attrs *unbound_attrs;
struct pool_workqueue *dfl_pwq;
char name[WQ_NAME_LEN];
struct rcu_head rcu;
unsigned int flags ____cacheline_aligned;
struct pool_workqueue __percpu *cpu_pwqs;
struct pool_workqueue __rcu *numa_pwq_tbl[];
};
Linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作,Linux 内核使用worker 结构体表示工作者线程,worker 结构体内容如下:
struct worker {
union {
struct list_head entry;
struct hlist_node hentry;
};
struct work_struct *current_work;
work_func_t current_func;
struct pool_workqueue *current_pwq;
struct list_head scheduled;
struct task_struct *task;
struct worker_pool *pool;
struct list_head node;
unsigned long last_active;
unsigned int flags;
int id;
int sleeping;
char desc[WORKER_DESC_LEN];
struct workqueue_struct *rescue_wq;
work_func_t last_func;
};
每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。
①INIT_WORK
简单创建工作很简单,直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作,INIT_WORK 宏定义如下:
#define INIT_WORK(_work, _func)
_work 表示要初始化的工作,_func 是工作对应的处理函数。也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:
#define DECLARE_WORK(n, f)
n 表示定义的工作(work_struct),f 表示工作对应的处理函数。
②schedule_work
和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原型如下所示:
bool schedule_work(struct work_struct *work)
函数参数和返回值含义如下:
work:要调度的工作。
**返回值:**0 成功,其他值 失败
工作队列使用框架:
/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 work */
schedule_work(&testwork);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 work */
INIT_WORK(&testwork, testwork_func_t);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
三、设备树中的中断
interrupt-parent = <&GPIO1>
interrupts = <5 ris>
5为硬件中断号,xlate解析设备树信息,irq_domain会将5转化为虚拟中断号传递给platform_device
之后就可以使用request_irq了
设备树中中断节点语法:
硬件框图:
在硬件上,“中断控制器”只有 GIC 这一个,但是我们在软件上也可以把上图中的“GPIO”称为“中断控制器”。很多芯片有多个 GPIO 模块,比如 GPIO1、GPIO2 等等。所以软件上的“中断控制器”就有很多个:GIC、GPIO1、GPIO2 等等。
GPIO1 连接到 GIC,GPIO2 连接到 GIC,所以 GPIO1 的父亲是 GIC,GPIO2 的父亲是 GIC。
1、中断控制器的三个主要属性:
compatible = {...};
/* 表明它是“中断控制器 */
interrupt-controller;
/* 表明引用这个中断控制器的话需要多少个 cell,
① #interrupt-cells=<1>
别的节点要使用这个中断控制器时,只需要一个 cell 来表明使用“哪一个中断”,指明中断类型。
② #interrupt-cells=<2>
别的节点要使用这个中断控制器时,需要一个 cell 来表明使用“哪一个中断 还需要另一个 cell 来描述中断,一般是表明触发类型”
第 2 个 cell 的 bits[3:0] 用来表示中断触发类型(trigger type and level flags):
1 = low-to-high edge triggered,上升沿触发
2 = high-to-low edge triggered,下降沿触发
4 = active high level-sensitive,高电平触发
8 = active low level-sensitive,低电平触发 */
#interrupt-cells = <1>;
需要指定使用的是哪个中断控制器的哪一个中断
interrupt-parent = <&GPIO1>
interrupts = <5 ris>
/* 对于 ARM 处理的GIC 来说,一共有 3 个 cells,这三个 cells 的含义如下:第一个 cells:中断类型,0 表示 SPI 中断,1 表示 PPI 中断。第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 32~287(256 个),对于 PPI 中断来说中断号的范围为 16~31,但是该 cell 描述的中断号是从 0 开始。第三个 cells:标志,bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。bit[15:8]为 PPI 中断的 CPU 掩码。*/
2、中断相关api
① request_irq
request_irq 函数用于申请中断,request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断
原型:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了
使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。
void free_irq(unsigned int irq, void *dev)
irq:要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
使用 request_irq 函数申请中断的时候需要设置中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)
第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构
②enable_irq disable_irq
常用的中断使用和禁止函数
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
enable_irq 和 disable_irq 用于使能和禁止指定的中断,irq 就是要禁止的中断号。
③disable_irq_nosync
disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:
void disable_irq_nosync(unsigned int irq)
disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。
④local_irq_enable
有时候我们需要关闭当前处理器的整个中断系统,也就是在学习 STM32 的时候常说的关闭全局中断,这个时候可以使用如下两个函数
local_irq_enable()
local_irq_disable()
local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。
local_irq_restore 用于恢复中断,将中断到 flags 状态。
四、Firefly-rk3399实际操作
1、修改设备树:
①pinctl子系统使能GPIO0_B4、GPIO4_D4
key_diy@0: {
compatible = "rk3399, keydrv";
key-gpios = <&gpio0 12 GPIO_ACTIVE_HIGHT>
};
key_diy@1: {
compatible = "rk3399, keydrv";
key-gpios = <&gpio4 29 GPIO_ACTIVE_HIGHT>
};
②在此节点基础上增加中断信息
key_diy@0 {
compatible = "rk3399, keydrv";
// GPIO0_B4 高电平有效
gpios = <&gpio0 12 GPIO_ACTIVE_HIGH
&gpio4 29 GPIO_ACTIVE_HIGH>;
};
注意:
key_diy@0 {
compatible = "rk3399, keydrv";
// GPIO0_B4 高电平有效
key-gpios = <&gpio0 12 GPIO_ACTIVE_HIGH
&gpio4 29 GPIO_ACTIVE_HIGH>;
};
此写法调用of_gpio_count报错。
在设备树中,gpio =, gpios =, xxx-gpios =, 和 xxx-gpio = 都是用来配置 GPIO 引脚的属性,但它们的使用场景和具体含义有所不同。以下是它们的区别及用法:
1. gpio =
用途:用于描述单个 GPIO 引脚的配置。
语法:单个 GPIO 引脚的控制器、引脚编号和标志(例如 GPIO_ACTIVE_HIGH 或 GPIO_ACTIVE_LOW)。
场景:通常用于简单的单个 GPIO 控制设备,如 LED、开关等。
示例:
dts
复制代码
led {
compatible = "gpio-leds";
gpio = <&gpio0 10 GPIO_ACTIVE_HIGH>;
};
解释:表示使用 gpio0 控制器的第 10 个引脚,且该引脚为高电平激活。
2. gpios =
用途:用于描述多个 GPIO 引脚的配置。
语法:可以指定多个 GPIO 引脚,每个引脚由 <&gpioX pin_number gpio_flags> 描述,多个引脚之间使用逗号或换行分隔。
场景:适用于涉及多个 GPIO 引脚的设备,如多个 LED、多个按键输入等。
示例:
dts
复制代码
leds {
compatible = "gpio-leds";
gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>,
<&gpio1 5 GPIO_ACTIVE_LOW>;
};
解释:该设备使用了两个 GPIO 引脚:gpio0 控制器的第 10 引脚(高电平激活)和 gpio1 控制器的第 5 引脚(低电平激活)。
3. xxx-gpio =
用途:用于描述与特定功能相关的单个 GPIO 引脚。这里的 xxx 代表功能的名称,例如 reset, power, enable 等,通常与特定功能的硬件引脚相关。
语法:类似于 gpio =,但 xxx-gpio 用于描述特定功能的引脚,表示该引脚控制某个硬件功能。
场景:通常用于需要指定特定硬件功能的 GPIO 引脚,如重置引脚、使能引脚等。
示例:
dts
复制代码
reset {
compatible = "gpio-reset";
reset-gpio = <&gpio0 12 GPIO_ACTIVE_LOW>;
};
解释:表示使用 gpio0 控制器的第 12 引脚作为重置引脚,且该引脚的低电平激活。
4. xxx-gpios =
用途:用于描述与特定功能相关的多个 GPIO 引脚。和 gpios = 类似,但它适用于特定功能的多个 GPIO 引脚配置。
语法:可以列出多个 GPIO 引脚,每个引脚由 <&gpioX pin_number gpio_flags> 描述,通常用于描述与某个硬件功能相关的多个引脚,如多个按键输入、多个复位引脚等。
场景:用于涉及多个 GPIO 引脚的特定功能,例如多个按键、多个复位引脚等。
示例:
dts
复制代码
gpio_keys {
compatible = "gpio-keys";
key1-gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>;
key2-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
};
解释:表示 gpio_keys 设备使用了两个按键,每个按键都连接到不同的 GPIO 引脚,其中 key1 使用 gpio0 控制器的第 10 引脚(高电平激活),而 key2 使用 gpio1 控制器的第 12 引脚(低电平激活)。
总结:四者的区别
属性 用途 设备类型 语法示例
gpio = 单个 GPIO 引脚配置 描述单个 GPIO 引脚的设备 gpio = <&gpio0 10 GPIO_ACTIVE_HIGH>;
gpios = 多个 GPIO 引脚配置 描述多个 GPIO 引脚的设备 gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>, <&gpio1 5 GPIO_ACTIVE_LOW>;
xxx-gpio = 特定功能的单个 GPIO 引脚配置 描述与特定功能相关的单个 GPIO 引脚 reset-gpio = <&gpio0 12 GPIO_ACTIVE_LOW>;
xxx-gpios = 特定功能的多个 GPIO 引脚配置 描述与特定功能相关的多个 GPIO 引脚 key-gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>, <&gpio1 5 GPIO_ACTIVE_LOW>;
小结
gpio 和 gpios 用于描述 GPIO 引脚,区别在于是否涉及多个 GPIO 引脚。
xxx-gpio 和 xxx-gpios 用于描述与特定功能相关的 GPIO 引脚,区别在于单个 GPIO 配置与多个 GPIO 配置。
在实际应用中,gpio 和 gpios 多用于常见的 GPIO 控制,而 xxx-gpio 和 xxx-gpios 更多用于与特定硬件功能(如重置、按键等)相关的 GPIO 配置。
2、驱动编写
相关函数介绍(只写本次使用的):
int of_gpio_count(const struct device_node *np);
np:可以直接通过of_find_node_by_name获取,也可以通过pdev->dev.of_node得到
np: 设备树节点的指针,指向当前设备的设备树节点。设备树节点包含了设备的所有信息,包括与 GPIO 相关的配置。
返回值
返回一个整数,表示设备树中该设备节点下定义的 GPIO 引脚的数量。
返回负值时,表示发生了错误,常见的错误返回值包括:
-EINVAL:表示设备树节点没有 gpios 属性。
-ENODATA:设备树节点没有找到有效的 GPIO 数据。
kzalloc : kernel zero alloc
int of_get_gpio_flags(const struct device_node *np, int index, unsigned int *flags);
参数说明
np: 设备树节点指针,指向需要解析的设备树节点。该节点通常包含有 GPIO 配置的属性(例如 gpios)。
index: GPIO 引脚的索引,表示你希望获取的 GPIO 引脚在 gpios 属性数组中的位置。索引从 0 开始。
flags: 用于返回 GPIO 标志的指针。该指针指向一个 unsigned int 变量,函数会通过此变量返回与 GPIO 引脚相关的标志信息,例如引脚的方向、激活电平等。
返回值
成功:返回 0,并将 GPIO 标志存储在 flags 中。
失败:返回负数错误码,表示无法找到 GPIO 引脚或解析失败。
功能描述
of_get_gpio_flags 函数会根据传入的设备树节点 np 和 index,从设备树中解析出对应 GPIO 引脚的标志信息。设备树中的 GPIO 通常会包括以下标志信息:
GPIO 的激活电平(例如高电平激活或低电平激活)。
GPIO 的触发方式(例如边缘触发或电平触发)。
GPIO 的方向(输入或输出)
int gpio_to_irq(unsigned int gpio);
参数说明
gpio: 要转换为中断号的 GPIO 引脚的编号(不是描述符)。该引脚必须已被配置为能够生成中断(例如,通过设备树或通过 API 设置为中断触发)。
返回值
成功:返回一个中断号,表示该 GPIO 引脚关联的中断。
失败:返回一个负数错误码,表示 GPIO 引脚无法转换为中断号。常见的错误包括:
-EINVAL:表示该 GPIO 引脚没有配置为中断源,或者是一个无效的 GPIO 引脚。
-ENOTTY:表示该 GPIO 引脚无法支持中断。
功能描述
gpio_to_irq 函数将一个 GPIO 引脚的编号转换为一个有效的中断号。在 Linux 内核中,GPIO 引脚可以被配置为触发中断事件(例如,电平变化或边沿触发),gpio_to_irq 允许驱动程序通过获取中断号来绑定中断处理程序,从而在引脚的电平或状态发生变化时响应中断。
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *devname, void *dev_id);
参数说明
irq: 中断号,表示你希望请求的中断。这是一个整数,通常是由硬件或设备树提供,或者通过其他方式确定。
handler: 中断处理程序,是一个函数指针,指向该中断号发生时内核调用的函数。该函数将被内核调用来处理对应的中断事件。
flags: 中断标志,控制中断的触发方式和其他配置。例如,指定是上升沿、中断触发类型,是否允许共享中断等。常见的标志包括:
IRQF_TRIGGER_RISING:上升沿触发
IRQF_TRIGGER_FALLING:下降沿触发
IRQF_SHARED:表示允许多个设备共享同一个中断号
IRQF_DISABLED:在处理中断时禁用本地中断(通常仅用于某些内核的低层实现)
devname: 设备名称,用于标识请求该中断的设备。它通常是一个字符串,表示设备的名字,例如 "gpio_button"。随便写
dev_id: 设备特定数据,它通常是设备的指针或者与该设备相关的结构体的指针。它会传递给中断处理程序,以便在处理中断时访问特定设备的状态或数据。
返回值
成功:返回 0,表示成功注册了中断处理程序。
失败:返回一个负数错误码,表示请求中断失败。常见的错误码包括:
-EBUSY:表示中断号已经被其他处理程序占用。
-EINVAL:表示提供的参数无效。
功能描述
request_irq 函数将指定的中断号与一个处理程序(IRQ handler)进行绑定。内核将会在该中断号触发时调用处理程序。在中断处理程序中,你可以根据需要处理硬件中断事件,比如读取数据、更新设备状态等。
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
struct gpio_key {
int gpio;
int irq;
enum of_gpio_flags flags;
};
struct gpio_key *gpios_key;
static irqreturn_t gpio_key_rk3399(int irq, void *dev_id)
{
struct gpio_key *gpios_key1 = dev_id;
printk("key %d val %d\r\n", irq, gpio_get_value(gpios_key1->gpio));
return IRQ_HANDLED;
}
static int rk3399_key_probe(struct platform_device *pdev) {
int count = 0;
int i = 0;
int err = 0;
printk("enter interrupt\r\n");
/* 获取gpio数 */
count = of_gpio_count(pdev->dev.of_node);
printk("count is %d\r\n", count);
gpios_key = kzalloc(count * sizeof(struct gpio_key), GFP_KERNEL);
/* 设置中断方式 */
for(i=0; i<count; i++) {
gpios_key[i].gpio = of_get_gpio_flags(pdev->dev.of_node, i, &(gpios_key[i].flags));
gpios_key[i].irq = gpio_to_irq(gpios_key[i].gpio);
err = request_irq(gpios_key[i].irq, gpio_key_rk3399, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "rk3399_key", &gpios_key[i]);
}
return err;
}
static int rk3399_key_remove(struct platform_device *pdev) {
int count = 0;
int i = 0;
count = of_gpio_count(pdev->dev.of_node);
for(i=0; i<count; i++) {
free_irq(gpios_key[i].irq, &gpios_key[i]);
}
return 0;
}
static const struct of_device_id firefly_rk3399_key[] = {
{ .compatible = "rk3399, keydrv" },
{},
};
struct platform_driver key_interrupt_drv = {
.probe = rk3399_key_probe,
.remove = rk3399_key_remove,
.driver = {
/* 匹配platform_device */
.name = "rk3399",
/* 匹配设备树platform_device */
.of_match_table = firefly_rk3399_key,
},
};
/* 入口函数 申请platform设备驱动 */
static int gpio_interrupt_init(void) {
return platform_driver_register(&key_interrupt_drv);
}
static void gpio_interrupt_exit(void) {
platform_driver_unregister(&key_interrupt_drv);
}
module_init(gpio_interrupt_init);
module_exit(gpio_interrupt_exit);
MODULE_LICENSE("GPL");
编译内核:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
编译设备树:
make dtbs ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
cp arch/arm64/boot/dts/rockchip/rk3399-firefly-linux.dtb boot/rk3399.dtb
genext2fs -b 32768 -B $((64*1024*1024/32768)) -d boot/ -i 8192 -U boot.img
编译驱动:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-