瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取文档+例程)
【视频观看】嵌入式学习之Linux驱动(第五期_中断_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
在上一章中,我们简单的认识了一下中断以及中断子系统框架,最后编写了中断申请和中断服务函数的实验,大家会发现虽然前面讲解的只是点很多,但实际用起来只需要两三个函数就可以了,但中断的具体申请流程是怎样的呢,大家就不是很清楚了,在本章节将带领大家研究中断的申请流程。
中断申请使用的是request_irq 函数,它用于请求一个中断号(IRQ number)并将一个中断处理程序与该中断关联起来,它定义在内核源码的“/include/linux/interrupt.h”目录下,具体定义如下所示:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
从上面的内容可以得到request_irq()函数实际上是调用了request_threaded_irq()函数来完成中断申请的过程。request_threaded_irq()函数提供了线程化的中断处理方式,可以在中断上下文中执行中断处理函数。
request_threaded_irq 函数是 Linux 内核提供的一个功能强大的函数,用于请求分配一个中断,并将中断处理程序与该中断关联起来。该函数的主要作用是在系统中注册中断处理函数,以响应对应中断的发生。以下是 request_threaded_irq 函数的功能和作用的详细介绍:
(1)中断请求:request_threaded_irq 函数用于请求一个中断。它会向内核注册对应中断号的中断处理函数,并为该中断分配必要的资源。中断号是标识特定硬件中断的唯一标识符。
(2)中断处理函数关联:通过 handler 参数,将中断处理函数与中断号关联起来。中断处理函数是一个预定义的函数,用于处理中断事件。当中断发生时,内核将调用该函数来处理中断事件。
(3)线程化中断处理:request_threaded_irq 函数还支持使用线程化中断处理函数。通过指定 thread_fn 参数,可以在一个内核线程上下文中异步执行较长时间的中断处理或延迟敏感的工作。这有助于避免在中断上下文中阻塞时间过长。
(4)中断属性设置:通过 irqflags 参数,可以设置中断处理的各种属性和标志。例如,可以指定中断触发方式(上升沿、下降沿、边沿触发等)、中断类型(边沿触发中断、电平触发中断等)以及其他特定的中断行为。
(5)设备标识关联:通过 dev_id 参数,可以将中断处理与特定设备关联起来。这样可以在中断处理函数中访问与设备相关的数据。设备标识符可以是指向设备结构体或其他与设备相关的数据的指针。
(6)错误处理:request_threaded_irq 函数会返回一个整数值,用于指示中断请求的结果。如果中断请求成功,返回值为 0;如果中断请求失败,则返回一个负数错误代码,表示失败的原因。
request_threaded_irq 函数定义在内核源码目录下的“/kernel/irq/manage.c”文件中,具体内容如下所示:
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action; // 中断动作结构体指针
struct irq_desc *desc; // 中断描述符指针
int retval; // 返回值
// 检查中断号是否为未连接状态
if (irq == IRQ_NOTCONNECTED)
return -ENOTCONN;
// 检查中断标志的有效性
if (((irqflags & IRQF_SHARED) && !dev_id) ||
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL;
// 根据中断号获取中断描述符
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
// 检查中断设置是否可以进行中断请求,以及是否为每个CPU分配唯一设备ID
if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
// 如果未指定中断处理函数,则使用默认的主处理函数
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
// 分配并初始化中断动作数据结构
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler; // 中断处理函数
action->thread_fn = thread_fn; // 线程处理函数
action->flags = irqflags; // 中断标志
action->name = devname; // 设备名称
action->dev_id = dev_id; // 设备ID
// 获取中断的电源管理引用计数
retval = irq_chip_pm_get(&desc->irq_data);
if (retval < 0) {
kfree(action);
return retval;
}
// 设置中断并将中断动作与中断描述符关联
retval = __setup_irq(irq, desc, action);
// 处理中断设置失败的情况
if (retval) {
irq_chip_pm_put(&desc->irq_data);
kfree(action->secondary);
kfree(action);
}
#ifdef CONFIG_DEBUG_SHIRQ_FIXME
if (!retval && (irqflags & IRQF_SHARED)) {
unsigned long flags;
disable_irq(irq);
local_irq_save(flags);
handler(irq, dev_id);
local_irq_restore(flags);
enable_irq(irq);
}
#endif
return retval; // 返回设置中断的结果
}
(1)声明变量和初始化:
struct irqaction *action; // 中断动作结构体指针
struct irq_desc *desc; // 中断描述符指针
int retval; // 返回值
第5行:用于存储中断动作结构体的指针(会在下面的小节进行详细的讲解)。
第6行:用于存储中断描述符的指针(会在下面的小节进行详细的讲解)。
第7行:用于存储函数的返回值。
(2)参数检查:
// 检查中断号是否为未连接状态
if (irq == IRQ_NOTCONNECTED)
return -ENOTCONN;
// 检查中断标志的有效性
if (((irqflags & IRQF_SHARED) && !dev_id) ||
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL;
第10行:检查中断号是否为未连接状态(IRQ_NOTCONNECTED)。
第14-17行:检查中断标志的有效性,包括共享标志与设备ID的关联性,条件挂起标志的有效性,以及无挂起标志与条件挂起标志的关联性。
(3)获取中断描述符:
// 根据中断号获取中断描述符
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
第20行:根据中断号调用irq_to_desc函数获取对应的中断描述符。
第21行:如果获取中断描述符失败,则返回-EINVAL表示无效的参数。
(4)检查中断设置:
// 检查中断设置是否可以进行中断请求,以及是否为每个CPU分配唯一设备ID
if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
第25-26行:检查中断设置是否可以进行中断请求,以及是否为每个CPU分配唯一设备ID。如果中断设置不满足要求,则返回-EINVAL表示无效的参数。
(5)处理中断处理函数和线程处理函数:
// 如果未指定中断处理函数,则使用默认的主处理函数
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
如果未指定中断处理函数,则将默认的主处理函数(irq_default_primary_handler)赋值给handler。
(6)分配并初始化中断动作数据结构:
// 分配并初始化中断动作数据结构
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler; // 中断处理函数
action->thread_fn = thread_fn; // 线程处理函数
action->flags = irqflags; // 中断标志
action->name = devname; // 设备名称
action->dev_id = dev_id; // 设备ID
第37行:调用kzalloc函数分配内存空间,大小为sizeof(struct irqaction)。
第38行:如果分配内存失败,则返回-ENOMEM表示内存不足。
第41行-第45行:将中断处理函数、线程处理函数、中断标志、设备名称和设备ID赋值给相应的字段。
(7)获取中断的电源管理引用计数:
// 获取中断的电源管理引用计数
retval = irq_chip_pm_get(&desc->irq_data);
if (retval < 0) {
kfree(action);
return retval;
}
第48行:调用irq_chip_pm_get函数获取中断的电源管理引用计数。
第49行:如果获取失败,则释放先前分配的内存空间,并返回获取失败的结果。
(8)设置中断并关联中断动作:
// 设置中断并将中断动作与中断描述符关联
retval = __setup_irq(irq, desc, action);
第55行:调用__setup_irq函数设置中断并将中断动作与中断描述符关联。
(9)处理设置中断失败的情况:
// 处理中断设置失败的情况
if (retval) {
irq_chip_pm_put(&desc->irq_data);
kfree(action->secondary);
kfree(action);
}
第59行:调用irq_chip_pm_put函数释放中断的电源管理引用计数。
第60行:释放次要中断动作的内存空间。
第61行:释放中断动作的内存空间。
(10)可选的共享中断处理:
#ifdef CONFIG_DEBUG_SHIRQ_FIXME
if (!retval && (irqflags & IRQF_SHARED)) {
unsigned long flags;
disable_irq(irq);
local_irq_save(flags);
handler(irq, dev_id);
local_irq_restore(flags);
enable_irq(irq);
}
#endif
第65行:如果设置中断成功且中断标志中包含共享标志(IRQF_SHARED),则执行以下操作:
第68行:禁用中断。
第69行:保存当前中断状态并禁用本地中断。
第70行:调用主处理函数处理中断。
第73行:恢复中断状态。
第74行):重新使能中断。
irq_desc 结构体是 Linux 内核中用于描述中断的数据结构之一。每个硬件中断都有一个对应的 irq_desc 实例,它用于记录与该中断相关的各种信息和状态。该结构体的主要功能是管理中断处理函数、中断行为以及与中断处理相关的其他数据。
以下是 irq_desc 结构体的主要作用和功能:
(1)中断处理函数管理:irq_desc 结构体中的 handle_irq 字段保存中断处理函数的指针。当硬件触发中断时,内核会调用该函数来处理中断事件。
(2)中断行为管理:irq_desc 结构体中的 action 字段是一个指向中断行为列表的指针。中断行为是一组回调函数,用于注册、注销和处理与中断相关的事件。
(3)中断统计信息:irq_desc 结构体中的 kstat_irqs 字段是一个指向中断统计信息的指针。该信息用于记录中断事件的发生次数和处理情况,可以帮助分析中断的性能和行为。
(4)中断数据管理:irq_desc 结构体中的 irq_data 字段保存了与中断相关的数据,如中断号、中断类型等。这些数据用于识别和管理中断。
(5)通用中断数据管理:irq_desc 结构体中的 irq_common_data 字段保存了与中断处理相关的通用数据,如中断控制器、中断屏蔽等。这些数据用于处理和控制中断的行为。
(6)中断状态管理:irq_desc 结构体中的其他字段用于管理中断的状态,如嵌套中断禁用计数、唤醒使能计数等。这些状态信息帮助内核跟踪和管理中断的状态变化。
通过使用 irq_desc 结构体,内核可以有效地管理和处理系统中的硬件中断。它提供了一个统一的接口,用于注册和处理中断处理函数、管理中断行为,并提供了必要的信息和数据结构来监视和控制中断的行为和状态。
irq_desc 结构体定义在内核源码目录的“include/linux/irqdesc.h”文件,具体内容如下所示:
struct irq_desc {
struct irq_common_data irq_common_data; /* 通用中断数据 */
struct irq_data irq_data; /* 中断数据 */
unsigned int __percpu *kstat_irqs; /* 中断统计信息 */
irq_flow_handler_t handle_irq; /* 中断处理函数 */
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler; /* 预处理中断处理函数 */
#endif
struct irqaction *action; * IRQ action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it; /* 内核内部状态标志位,请勿修改 */
unsigned int depth; /* 嵌套中断禁用计数 */
unsigned int wake_depth; /* 嵌套唤醒使能计数 */
unsigned int tot_count;
unsigned int irq_count; /* 用于检测损坏的IRQ计数 */
unsigned long last_unhandled; /* 未处理计数的老化计时器 */
unsigned int irqs_unhandled; /* 未处理的中断计数 */
atomic_t threads_handled; /* 处理中断的线程计数 */
int threads_handled_last;
raw_spinlock_t lock; /* 自旋锁 */
struct cpumask *percpu_enabled; /* 指向每个CPU的使能掩码 */
const struct cpumask *percpu_affinity; /* 指向每个CPU亲和性掩码 */
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint; /* CPU亲和性提示 */
struct irq_affinity_notify *affinity_notify; /* CPU亲和性变化通知 */
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask; /* 等待处理的中断掩码 */
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active; /* 活动中的线程计数 */
wait_queue_head_t wait_for_threads; /* 等待线程的等待队列头 */
#ifdef CONFIG_PM_SLEEP
unsigned int nr_actions;
unsigned int no_suspend_depth;
unsigned int cond_suspend_depth;
unsigned int force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir; /* proc文件系统目录项 */
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
struct dentry *debugfs_file; /* 调试文件系统文件 */
const char *dev_name; /* 设备名称 */
#endif
#ifdef CONFIG_SPARSE_IRQ
struct rcu_head rcu;
struct kobject kobj; /* 内核对象 */
#endif
struct mutex request_mutex; /* 请求互斥锁 */
int parent_irq; /* 父中断号 */
struct module *owner; /* 模块拥有者 */
const char *name; /* 中断名称 */
} ____cacheline_internodealigned_in_smp;
在irq_desc 结构体中最重要的就是 action 字段,会在下个小节对action 字段进行详细的讲解。
irqaction 结构体是 Linux 内核中用于描述中断行为的数据结构之一。它用于定义中断处理过程中的回调函数和相关属性。irqaction 结构体的主要功能是管理与特定中断相关的行为和处理函数。
以下是 irqaction 结构体的主要作用和功能:
(1)中断处理函数管理:irqaction 结构体中的 handler 字段保存中断处理函数的指针。该函数在中断发生时被调用,用于处理中断事件。
(2)中断处理标志管理:irqaction 结构体中的 flags 字段用于指定中断处理的各种属性和标志。这些标志控制中断处理的行为,例如触发方式、中断类型等。
(3)设备标识符管理:irqaction 结构体中的 dev_id 字段用于保存与中断处理相关的设备标识符。它可以是指向设备结构体或其他与设备相关的数据的指针,用于将中断处理与特定设备关联起来。
(4)中断行为链表管理:irqaction 结构体中的 next 字段是一个指向下一个 irqaction 结构体的指针,用于构建中断行为的链表。这样可以将多个中断处理函数链接在一起,以便在中断发生时按顺序调用它们。
通过使用 irqaction 结构体,内核可以灵活地定义和管理与特定中断相关的行为和处理函数。它提供了一个统一的接口,用于注册和注销中断处理函数,并提供了必要的属性和数据结构来控制中断处理的行为和顺序。
irqaction 体定义在内核源码的“include/linux/interrupt.h”文件中如下所示:
struct irqaction {
irq_handler_t handler; // 中断处理函数
void *dev_id; // 设备ID
void __percpu *percpu_dev_id; // 每个CPU的设备ID
struct irqaction *next; // 下一个中断动作结构体
irq_handler_t thread_fn; // 线程处理函数
struct task_struct *thread; // 线程结构体指针
struct irqaction *secondary; // 次要中断动作结构体
unsigned int irq; // 中断号
unsigned int flags; // 中断标志
unsigned long thread_flags; // 线程标志
unsigned long thread_mask; // 线程掩码
const char *name; // 设备名称
struct proc_dir_entry *dir; // proc文件系统目录项指针
} ____cacheline_internodealigned_in_smp;