本文代码基于6.17-6.19 (最开始写中断文章的时候只有6.17,写着写着就6.19了)
阅读之前,先留给各位读者两个问题
中断线程是不是和名字一样,工作在进程上下文?
中断线程工作在进程上下文,如何确保实时性? 何时唤醒?
关于中断的核心知识,可以查看版主之前的文章:
https://www.iliuqi.com/archives/linux-aarch64-all
里面也有笔者绘制的中断流程图,整体逻辑比较完整。
核心结构体
struct irqaction
用于描述一个irqaction,我们使用request_irq()注册一个中断的时候,实际上就是注册一个action。action和设备通常是一一对应的。
如果开启了共享中断,一条中断线上可以有多个action。
/**
* struct irqaction - per interrupt action descriptor
* @handler: interrupt handler function
* @name: name of the device
* @dev_id: cookie to identify the device
* @percpu_dev_id: cookie to identify the device
* @next: pointer to the next irqaction for shared interrupts
* @irq: interrupt number
* @flags: flags (see IRQF_* above)
* @thread_fn: interrupt handler function for threaded interrupts
* @thread: thread pointer for threaded interrupts
* @secondary: pointer to secondary irqaction (force threading)
* @thread_flags: flags related to @thread
* @thread_mask: bitmask for keeping track of @thread activity
* @dir: pointer to the proc/irq/NN/name entry
*/
struct irqaction {
irq_handler_t handler; /* 此action对应的硬件中断处理函数 */
void *dev_id;//对应的设备id
void __percpu *percpu_dev_id;
//链表中下一个中断服务例程
struct irqaction *next;
irq_handler_t thread_fn;//中断线程服务函数
struct task_struct *thread;//中断通用线程的task_struct
struct irqaction *secondary;
unsigned int irq;//irq号,或者说irq线
unsigned int flags;//描述irq的标识符,IRQF_
unsigned long thread_flags;
unsigned long thread_mask;//用于屏蔽对应的中断线
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;核心flag
IRQF_ONESHOT
这个flag用于保证中断线程仍在执行的时候,该中断线暂时被屏蔽。
在中断初始化的时候,如果检测到这个flag,会初始化对应的action->thread_mask
在中断流处理函数如handle_level_irq()时,会先mask该中断,硬件中断函数处理完之后,会唤醒中断线程处理下半部:
此时会通过action->thread_mask设置desc->threads_oneshot,desc->threads_oneshot不为0说明此中断线当前有中断线程正在处理或准备处理,并原子增加desc->threads_active计数,计数表明当前此中断线上running的中断线程的数量。
当中断线程处理完工作之后,会调用irq_finalize_oneshot(),清除此action的mask位:desc->threads_oneshot &= ~action->thread_mask,如果此desc->threads_oneshot为0,说明此中断线上所有中断线程已经执行完成了,unmask该中断。之后会原子减少desc->threads_active计数。
IRQTF_RUNTHREAD
硬件中断函数执行完之后,会调用__irq_wake_thread()尝试唤醒中断线程,此时会设置这个flag:
test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags)
IRQTF_RUNTHREAD会作为 中断线程 是否可以开始工作的一个依据,中断线程会检查。
IRQD_IRQ_MASKED
表明此中断被mask,这是针对中断线而言的
核心函数
request_threaded_irq()
调用方法类似于:
error = request_threaded_irq(irq, NULL, sh_keysc_isr, IRQF_ONESHOT,dev_name(&pdev->dev), pdev);
这里没有指定 硬中断服务函数,只指定了中断线程,相当于完全线程化,不指定硬件中断处理函数,目前来看这种使用方式还是非常常见的。
irq:中断线/中断号
handler:硬件中断处理函数
thread_fn:中断下半部线程
irqflags:IRQ FLAG
devname:设备名
dev_id:传给handler的传参
中断线程化常和IRQF_ONESHOT联动使用:
当硬中断 handler 返回后,不会立即重新使能中断线。
中断线保持禁用状态,直到对应的 线程化 handler 完成,内核才会重新使能中断。防止中断多次触发影响中断线程处理
/**
* request_threaded_irq - allocate an interrupt line
* @irq: Interrupt line to allocate
* @handler: Function to be called when the IRQ occurs.
* Primary handler for threaded interrupts.
* If handler is NULL and thread_fn != NULL
* the default primary handler is installed.
* @thread_fn: Function called from the irq handler thread
* If NULL, no irq thread is created
* @irqflags: Interrupt type flags
* @devname: An ascii name for the claiming device
* @dev_id: A cookie passed back to the handler function
*
* This call allocates interrupt resources and enables the interrupt line
* and IRQ handling. From the point this call is made your handler function
* may be invoked. Since your handler function must clear any interrupt the
* board raises, you must take care both to initialise your hardware and to
* set up the interrupt handler in the right order.
*
* If you want to set up a threaded irq handler for your device then you
* need to supply @handler and @thread_fn. @handler is still called in hard
* interrupt context and has to check whether the interrupt originates from
* the device. If yes it needs to disable the interrupt on the device and
* return IRQ_WAKE_THREAD which will wake up the handler thread and run
* @thread_fn. This split handler design is necessary to support shared
* interrupts.
*
* @dev_id must be globally unique. Normally the address of the device data
* structure is used as the cookie. Since the handler receives this value
* it makes sense to use it.
*
* If your interrupt is shared you must pass a non NULL dev_id as this is
* required when freeing the interrupt.
*
* Flags:
*
* IRQF_SHARED Interrupt is shared
* IRQF_TRIGGER_* Specify active edge(s) or level
* IRQF_ONESHOT Run thread_fn with interrupt line masked
*/
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;
/*
* Sanity-check: shared interrupts must pass in a real dev-ID,
* otherwise we'll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc).
*
* Also shared interrupts do not go well with disabling auto enable.
* The sharing interrupt might request it while it's still disabled
* and then wait for interrupts forever.
*
* Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
* it cannot be set along with IRQF_NO_SUSPEND.
*/
if (((irqflags & IRQF_SHARED) && !dev_id) ||
((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) ||
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL;
desc = irq_to_desc(irq);//irq号转中断描述符
if (!desc)
return -EINVAL;
if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
if (!handler) {
if (!thread_fn)
return -EINVAL;//没有中断服务函数也没有线程,返回
/*default handler会直接返回return IRQ_WAKE_THREAD */
//如果有线程,设置中断处理函数为default handler
handler = irq_default_primary_handler;
}
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);//为irqaction分配内存
if (!action)
return -ENOMEM;
//设置对应的action,一个action对应于一个具体的设备
action->handler = handler;//中断服务函数
action->thread_fn = thread_fn;//中断线程,绑定到action的thread_fn
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_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)) {
/*
* It's a shared IRQ -- the driver ought to be prepared for it
* to happen immediately, so let's make sure....
* We disable the irq to make sure that a 'real' IRQ doesn't
* run in parallel with our fake.
*/
unsigned long flags;
disable_irq(irq);
local_irq_save(flags);
handler(irq, dev_id);
local_irq_restore(flags);
enable_irq(irq);
}
#endif
return retval;
}
EXPORT_SYMBOL(request_threaded_irq);
💎IRQ注册核心 __setup_irq()
传参分别是,中断号,中断描述符,中断action(对应于一个外部设备)
如果设置了IRQF_ONESHOT,会在下面200行左右,初始化这个action的thread_mask,唤醒中断线程时,会用这个thread_mask保证对应中断线的屏蔽。
/*
* Internal function to register an irqaction - typically used to
* allocate special interrupts that are part of the architecture.
*
* Locking rules:
*
* desc->request_mutex Provides serialization against a concurrent free_irq()
* chip_bus_lock Provides serialization for slow bus operations
* desc->lock Provides serialization against hard interrupts
*
* chip_bus_lock and desc->lock are sufficient for all other management and
* interrupt related functions. desc->request_mutex solely serializes
* request/free_irq().
*/
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
struct irqaction *old, **old_ptr;
unsigned long flags, thread_mask = 0;
int ret, nested, shared = 0;
if (!desc)
return -EINVAL;
if (desc->irq_data.chip == &no_irq_chip)
return -ENOSYS;
if (!try_module_get(desc->owner))
return -ENODEV;
new->irq = irq;//设置irq号
/*
* If the trigger type is not specified by the caller,
* then use the default for this interrupt.
*/
if (!(new->flags & IRQF_TRIGGER_MASK))
new->flags |= irqd_get_trigger_type(&desc->irq_data);
/*
* Check whether the interrupt nests into another interrupt
* thread.
*/
//检查这个中断是不是嵌套线程中断,需要用irq_set_nested_thread()设置
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) {
ret = -EINVAL;
goto out_mput;
}
/*
* Replace the primary handler which was provided from
* the driver for non nested interrupt handling by the
* dummy function which warns when called.
*/
new->handler = irq_nested_primary_handler;
} else {
//大部分IRQ都不是nested,所以likely会走这里
if (irq_settings_can_thread(desc)) {
//在内核启用了 强制线程化中断 (forced interrupt threading) 的情况下,
//把一个普通的中断处理函数强制转换成线程化中断。成功返回0,一般不启用 强制线程化
//如果开启了PREEMPT_RT实时补丁,中断必须强制线程化
//内部也会设置action的secondary
ret = irq_setup_forced_threading(new);
if (ret)
goto out_mput;
}
}
/*
* Create a handler thread when a thread function is supplied
* and the interrupt does not nest into another interrupt
* thread.
*/
if (new->thread_fn && !nested) {
//调用setup函数,正式对中断线程进行初始化
ret = setup_irq_thread(new, irq, false);
if (ret)
goto out_mput;
if (new->secondary) {
//如果开启了中断强制线程化,还需要初始化secondary action
ret = setup_irq_thread(new->secondary, irq, true);
if (ret)
goto out_thread;
}
}
/*
* Drivers are often written to work w/o knowledge about the
* underlying irq chip implementation, so a request for a
* threaded irq without a primary hard irq context handler
* requires the ONESHOT flag to be set. Some irq chips like
* MSI based interrupts are per se one shot safe. Check the
* chip flags, so we can avoid the unmask dance at the end of
* the threaded handler for those.
*/
//检查中断控制器(irq_chip)是否自带 IRQCHIP_ONESHOT_SAFE 属性(如 MSI 中断)。
//如果是,内核会去掉 IRQF_ONESHOT 标志
if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
new->flags &= ~IRQF_ONESHOT;
/*
* Protects against a concurrent __free_irq() call which might wait
* for synchronize_hardirq() to complete without holding the optional
* chip bus lock and desc->lock. Also protects against handing out
* a recycled oneshot thread_mask bit while it's still in use by
* its previous owner.
*/
//防止两个进程同时对同一个 IRQ 进行申请或释放。
mutex_lock(&desc->request_mutex);
/*
* Acquire bus lock as the irq_request_resources() callback below
* might rely on the serialization or the magic power management
* functions which are abusing the irq_bus_lock() callback,
*/
chip_bus_lock(desc);
/* First installed action requests resources. */
if (!desc->action) {
ret = irq_request_resources(desc);
if (ret) {
pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
new->name, irq, desc->irq_data.chip->name);
goto out_bus_unlock;
}
}
/*
* The following block of code has to be executed atomically
* protected against a concurrent interrupt and any of the other
* management calls which are not serialized via
* desc->request_mutex or the optional bus lock.
*/
raw_spin_lock_irqsave(&desc->lock, flags);
old_ptr = &desc->action;
old = *old_ptr;
if (old) {
/*
* Can't share interrupts unless both agree to and are
* the same type (level, edge, polarity). So both flag
* fields must have IRQF_SHARED set and the bits which
* set the trigger type must match. Also all must
* agree on ONESHOT.
* Interrupt lines used for NMIs cannot be shared.
*/
unsigned int oldtype;
if (irq_is_nmi(desc)) {
pr_err("Invalid attempt to share NMI for %s (irq %d) on irqchip %s.\n",
new->name, irq, desc->irq_data.chip->name);
ret = -EINVAL;
goto out_unlock;
}
/*
* If nobody did set the configuration before, inherit
* the one provided by the requester.
*/
if (irqd_trigger_type_was_set(&desc->irq_data)) {
oldtype = irqd_get_trigger_type(&desc->irq_data);
} else {
oldtype = new->flags & IRQF_TRIGGER_MASK;
irqd_set_trigger_type(&desc->irq_data, oldtype);
}
if (!((old->flags & new->flags) & IRQF_SHARED) ||
(oldtype != (new->flags & IRQF_TRIGGER_MASK)))
goto mismatch;
if ((old->flags & IRQF_ONESHOT) &&
(new->flags & IRQF_COND_ONESHOT))
new->flags |= IRQF_ONESHOT;
else if ((old->flags ^ new->flags) & IRQF_ONESHOT)
goto mismatch;
/* All handlers must agree on per-cpuness */
if ((old->flags & IRQF_PERCPU) !=
(new->flags & IRQF_PERCPU))
goto mismatch;
/* add new interrupt at end of irq queue */
do {
/*
* Or all existing action->thread_mask bits,
* so we can find the next zero bit for this
* new action.
*/
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
}
/*
* Setup the thread mask for this irqaction for ONESHOT. For
* !ONESHOT irqs the thread mask is 0 so we can avoid a
* conditional in irq_wake_thread().
*/
if (new->flags & IRQF_ONESHOT) {
//如果设置了ONESHOT...
/*
* Unlikely to have 32 resp 64 irqs sharing one line,
* but who knows.
*/
if (thread_mask == ~0UL) {
ret = -EBUSY;
goto out_unlock;
}
/*
* 该 action 的 thread_mask 会被按位或(OR)操作到 desc->threads_active 中,
* 用于表示 IRQF_ONESHOT 的线程处理程序已被唤醒但尚未完成。
* 当线程完成时,对应的位会被清除。当共享中断线的所有线程都完成后,
* desc->threads_active 变为零,中断线将被解除屏蔽。
* 详见 handle.c:irq_wake_thread() 的实现。
*
* 如果主中断处理程序(硬中断上下文)没有唤醒任何线程,
* 则会在硬中断流处理程序(如 handle_[fasteoi|level]_irq)中
* 检查 desc->threads_active 是否为零以解除中断线的屏蔽。
*
* 新注册的 action 会分配到 thread_mask 的第一个零比特位。
* 参考上述循环中对所有现有 action->thread_mask 的按位或操作。
*/
//设置该中断对应action的thread_mask
new->thread_mask = 1UL << ffz(thread_mask);
} else if (new->handler == irq_default_primary_handler &&
!(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
/*
* The interrupt was requested with handler = NULL, so
* we use the default primary handler for it. But it
* does not have the oneshot flag set. In combination
* with level interrupts this is deadly, because the
* default primary handler just wakes the thread, then
* the irq lines is reenabled, but the device still
* has the level irq asserted. Rinse and repeat....
*
* While this works for edge type interrupts, we play
* it safe and reject unconditionally because we can't
* say for sure which type this interrupt really
* has. The type flags are unreliable as the
* underlying chip implementation can override them.
*/
pr_err("Threaded irq requested with handler=NULL and !ONESHOT for %s (irq %d)\n",
new->name, irq);
ret = -EINVAL;
goto out_unlock;
}
.........................
raw_spin_unlock_irqrestore(&desc->lock, flags);
chip_bus_sync_unlock(desc);
mutex_unlock(&desc->request_mutex);
irq_setup_timings(desc, new);
wake_up_and_wait_for_irq_thread_ready(desc, new);//确保对应的中断线程可以被唤醒
wake_up_and_wait_for_irq_thread_ready(desc, new->secondary);
register_irq_proc(irq, desc);
new->dir = NULL;
register_handler_proc(irq, new);
return 0;
mismatch:
if (!(new->flags & IRQF_PROBE_SHARED)) {
pr_err("Flags mismatch irq %d. %08x (%s) vs. %08x (%s)\n",
irq, new->flags, new->name, old->flags, old->name);
#ifdef CONFIG_DEBUG_SHIRQ
dump_stack();
#endif
}
ret = -EBUSY;
out_unlock:
raw_spin_unlock_irqrestore(&desc->lock, flags);
if (!desc->action)
irq_release_resources(desc);
out_bus_unlock:
chip_bus_sync_unlock(desc);
mutex_unlock(&desc->request_mutex);
out_thread:
if (new->thread) {
struct task_struct *t = new->thread;
new->thread = NULL;
kthread_stop_put(t);
}
if (new->secondary && new->secondary->thread) {
struct task_struct *t = new->secondary->thread;
new->secondary->thread = NULL;
kthread_stop_put(t);
}
out_mput:
module_put(desc->owner);
return ret;
}irq_setup_forced_threading()
这个函数主要做一些 中断强制线程化 的处理
如果中断不需要强制线程化,或者该中断已经线程化,退出,不需要执行操作
设置此线程化中断为IRQF_ONESHOT
如果原中断既有 硬件中断回调函数 又有 中断线程处理函数,需要设置secondary action,做额外的处理
secondary的handler设置为irq_forced_secondary_handler
secondary->thread_fn设置为primary action的thread_fn
名称、中断号、设备信息沿用primary action
设置IRQTF_FORCED_THREAD
设置primary action的硬中断处理函数为irq_default_primary_handler,中断线程函数为原本的硬中断处理函数。
static int irq_setup_forced_threading(struct irqaction *new)
{
//如果没有开启强制线程化,直接返回
if (!force_irqthreads())
return 0;
//如果中断的 flags 包含以下任意一个标记,则直接返回 0,表示不强制线程化
if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
return 0;
/*
* No further action required for interrupts which are requested as
* threaded interrupts already
*/
//如果当前中断的 handler 已经是默认的线程化主处理函数(irq_default_primary_handler)
//说明它已经被线程化,直接返回 0。
if (new->handler == irq_default_primary_handler)
return 0;
new->flags |= IRQF_ONESHOT;
/*
* Handle the case where we have a real primary handler and a
* thread handler. We force thread them as well by creating a
* secondary action.
*/
//如果中断既有主处理程序(handler)又有线程函数(thread_fn),
//则需要创建一个次级 irqaction(new->secondary)
//这个secondary也是一个irqaction类型的指针
if (new->handler && new->thread_fn) {
/* Allocate the secondary action */
new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!new->secondary)
return -ENOMEM;
//设置secondary的硬中断处理函数为irq_forced_secondary_handler
new->secondary->handler = irq_forced_secondary_handler;
new->secondary->thread_fn = new->thread_fn;
new->secondary->dev_id = new->dev_id;
new->secondary->irq = new->irq;
new->secondary->name = new->name;
}
/* Deal with the primary handler */
set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
new->thread_fn = new->handler;
new->handler = irq_default_primary_handler;
return 0;
}setup_irq_thread()
会使用kthread_create()创建内核线程,这些内核线程对应使用的函数都是irq_thread()。但命名不同。见下图,命名会跟中断名称&中断号相关
800%cpu 1%user 0%nice 3%sys 793%idle 0%iow 2%irq 1%sirq 0%host
735 root RT 0 0 0 0 S 0.3 0.0 0:09.10 [irq/210-dcvsh-irq-4]
11847 root 20 0 10G 4.8M 4.2M S 0.0 0.0 0:00.01 grep irq
11392 root GO ON
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
struct task_struct *t;
if (!secondary) {
//创建kthread,函数为irq_thread.
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
} else {
t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
new->name);
}
if (IS_ERR(t))
return PTR_ERR(t);
/*
* We keep the reference to the task struct even if
* the thread dies to avoid that the interrupt code
* references an already freed task_struct.
*/
new->thread = get_task_struct(t);//将创建的irq_thread赋值给action->thread
/*
* Tell the thread to set its affinity. This is
* important for shared interrupt handlers as we do
* not invoke setup_affinity() for the secondary
* handlers as everything is already set up. Even for
* interrupts marked with IRQF_NO_BALANCE this is
* correct as we want the thread to move to the cpu(s)
* on which the requesting code placed the interrupt.
*/
//告知中断线程,需要设置它的CPU亲和性
set_bit(IRQTF_AFFINITY, &new->thread_flags);
return 0;
}wake_up_and_wait_for_irq_thread_ready()
注意陷阱,setup_irq_thread()只是调用kthread_create(),并没有run,run包含了create & TTWU,此时irq_thread还没运行过呢!要在这里首次尝试唤醒!!
唤醒成功的话,IRQTF_READY才会被设置,然后再回过头来唤醒setup_irq_thread()这个进程。
这么做的意义可能就是确保这个中断线程可以被唤醒,可以工作。
/*
* Internal function to wake up a interrupt thread and wait until it is
* ready.
*/
static void wake_up_and_wait_for_irq_thread_ready(struct irq_desc *desc,
struct irqaction *action)
{
if (!action || !action->thread)
return;
wake_up_process(action->thread);
wait_event(desc->wait_for_threads,
test_bit(IRQTF_READY, &action->thread_flags));
}
❤通用中断线程 irq_thread()
/*
* Interrupt handler thread
*/
static int irq_thread(void *data)
{
struct callback_head on_exit_work;
struct irqaction *action = data;
struct irq_desc *desc = irq_to_desc(action->irq);
irqreturn_t (*handler_fn)(struct irq_desc *desc,
struct irqaction *action);
irq_thread_set_ready(desc, action);//设置对应的中断线程为IRQTF_READY
sched_set_fifo(current);//设置调度策略为FIFO
//设置为fifo,相当于将这个中断线程函数设置为了RT线程,RT线程优先级高于CFS线程,因此会被优先调度
if (force_irqthreads() && test_bit(IRQTF_FORCED_THREAD,
&action->thread_flags))
//中断强制线程化设置不一样的处理函数
handler_fn = irq_forced_thread_fn;
else
handler_fn = irq_thread_fn;//设置handler_fn为irq_thread_fn
init_task_work(&on_exit_work, irq_thread_dtor);
task_work_add(current, &on_exit_work, TWA_NONE);
while (!irq_wait_for_interrupt(desc, action)) {
irqreturn_t action_ret;//用于保存返回值
action_ret = handler_fn(desc, action);
if (action_ret == IRQ_WAKE_THREAD)
//如果primary action的线程处理函数返回IRQ_WAKE_THREAD
//说明在线程化之前,它是一个硬件处理函数,且有对应的线程处理函数
//因此在这里唤醒对应的secondary action的irq thread
irq_wake_secondary(desc, action);
wake_threads_waitq(desc);//这里会减少threads_active计数
}
/*
* This is the regular exit path. __free_irq() is stopping the
* thread via kthread_stop() after calling
* synchronize_hardirq(). So neither IRQTF_RUNTHREAD nor the
* oneshot mask bit can be set.
*/
task_work_cancel_func(current, irq_thread_dtor);
return 0;
}irq_thread_set_ready()
告知内核&前面的wait,该中断线程已经被成功唤醒,就绪。
/*
* Internal function to notify that a interrupt thread is ready.
*/
static void irq_thread_set_ready(struct irq_desc *desc,
struct irqaction *action)
{
set_bit(IRQTF_READY, &action->thread_flags);
wake_up(&desc->wait_for_threads);
}irq_wait_for_interrupt()
static int irq_wait_for_interrupt(struct irq_desc *desc,
struct irqaction *action)
{
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);//S状态
irq_thread_check_affinity(desc, action);//绑核
if (kthread_should_stop()) {
//如果判断这个内核线程需要被终止,但如果IRQTF_RUNTHREAD被设置
//说明被唤醒,再工作最后一次
/* may need to run one last time */
if (test_and_clear_bit(IRQTF_RUNTHREAD,
&action->thread_flags)) {
__set_current_state(TASK_RUNNING);
return 0;
}
__set_current_state(TASK_RUNNING);//设置R状态
return -1;
}
//如果不需要被终止,简单检查一下IRQTF_RUNTHREAD有没有被唤醒流程设置
if (test_and_clear_bit(IRQTF_RUNTHREAD,
&action->thread_flags)) {
__set_current_state(TASK_RUNNING);//设置R状态
return 0;
}
schedule();//没人唤醒我,S状态,调度
}
}
irq_thread_fn()
调用对应中断action的 中断线程函数
/*
* Interrupts explicitly requested as threaded interrupts want to be
* preemptible - many of them need to sleep and wait for slow busses to
* complete.
*/
static irqreturn_t irq_thread_fn(struct irq_desc *desc, struct irqaction *action)
{
//调用对应的中断线程处理函数
irqreturn_t ret = action->thread_fn(action->irq, action->dev_id);
if (ret == IRQ_HANDLED)
atomic_inc(&desc->threads_handled);//增加中断线程处理的计数
irq_finalize_oneshot(desc, action);//善后,判断当前中断线是不是可以取消mask
return ret;
}irq_finalize_oneshot()
/*
* Oneshot interrupts keep the irq line masked until the threaded
* handler finished. unmask if the interrupt has not been disabled and
* is marked MASKED.
*/
static void irq_finalize_oneshot(struct irq_desc *desc,
struct irqaction *action)
{
if (!(desc->istate & IRQS_ONESHOT) ||
action->handler == irq_forced_secondary_handler)
//如果不是IRQS_ONESHOT,直接返回
return;
again:
chip_bus_lock(desc);
raw_spin_lock_irq(&desc->lock);//上中断描述符的spinlock
/*
* Implausible though it may be we need to protect us against
* the following scenario:
*
* The thread is faster done than the hard interrupt handler
* on the other CPU. If we unmask the irq line then the
* interrupt can come in again and masks the line, leaves due
* to IRQS_INPROGRESS and the irq line is masked forever.
*
* This also serializes the state of shared oneshot handlers
* versus "desc->threads_oneshot |= action->thread_mask;" in
* irq_wake_thread(). See the comment there which explains the
* serialization.
*/
if (unlikely(irqd_irq_inprogress(&desc->irq_data))) {
raw_spin_unlock_irq(&desc->lock);
chip_bus_sync_unlock(desc);
cpu_relax();
goto again;
}
/*
* Now check again, whether the thread should run. Otherwise
* we would clear the threads_oneshot bit of this thread which
* was just set.
*/
if (test_bit(IRQTF_RUNTHREAD, &action->thread_flags))
goto out_unlock;
//清楚在唤醒irq thread过程中设置的desc->threads_oneshot
desc->threads_oneshot &= ~action->thread_mask;
if (!desc->threads_oneshot && !irqd_irq_disabled(&desc->irq_data) &&
irqd_irq_masked(&desc->irq_data))
//只有当中断线上所有中断线程处理完成并清除其对应的掩码位后,threads_oneshot 才会变为 0,
//此时内核才会重新启用硬件中断。需要先检查IRQD_IRQ_MASKED
unmask_threaded_irq(desc);//这里会取消之前这个中断线的mask状态
out_unlock:
raw_spin_unlock_irq(&desc->lock);
chip_bus_sync_unlock(desc);
}中断线程唤醒流程:
注意,这里是在硬件中断上下文
handle_irq_event()
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING;//取消之前设置的PENDING状态
//将对应中断线设置为IRQD_IRQ_INPROGRESS状态
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
//此函数会遍历中断线上所有的action,并处理各个action的回调函数
ret = handle_irq_event_percpu(desc);
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}handle_irq_event_percpu()
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
irqreturn_t retval;
retval = __handle_irq_event_percpu(desc);
add_interrupt_randomness(desc->irq_data.irq);
if (!irq_settings_no_debug(desc))
note_interrupt(desc, retval);
return retval;
}helper:
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
record_irq_time(desc);
for_each_action_of_desc(desc, action) {
//遍历中断线上的所有action
irqreturn_t res;//res用于保存返回值
/*
* If this IRQ would be threaded under force_irqthreads, mark it so.
*/
if (irq_settings_can_thread(desc) &&
!(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
lockdep_hardirq_threaded();
trace_irq_handler_entry(irq, action);
if (static_branch_unlikely(&irqhandler_duration_check_enabled)) {
u64 ts_start = local_clock();
res = action->handler(irq, action->dev_id);
irqhandler_duration_check(ts_start, irq, action);
} else {
res = action->handler(irq, action->dev_id);//调用底层设备的硬中断处理函数
}
trace_irq_handler_exit(irq, action, res);//获取res返回值
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
irq, action->handler))
local_irq_disable();
/* 确保到这里还是禁用了本地中断 */
switch (res) {
case IRQ_WAKE_THREAD://如果是有中断线程的中断
//设备中断处理函数会返回这个,告知内核接下来需要唤醒对应的irq_thread去进程上下文办公
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
__irq_wake_thread(desc, action);
break;
default:
break;
}
retval |= res;
}
return retval;
}唤醒中断线程 __irq_wake_thread()
唤醒时,会设置IRQTF_RUNTHREAD
此函数只是使用TTWU唤醒中断线程,将中断线程加入rq,真正运行还得是进程上下文
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
{
/*
* In case the thread crashed and was killed we just pretend that
* we handled the interrupt. The hardirq handler has disabled the
* device interrupt, so no irq storm is lurking.
*/
//如果中断线程已经被标记为 PF_EXITING(例如线程崩溃或被终止),
//则直接返回,避免唤醒无效线程。
if (action->thread->flags & PF_EXITING)
return;
/*
* Wake up the handler thread for this action. If the
* RUNTHREAD bit is already set, nothing to do.
*/
//设置IRQTF_RUNTHREAD,当中断线程再次被调度时,发现这给flag被设置
//会退出等待,并设置state为TASK_RUNNING
if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags))
return;
/*
* It's safe to OR the mask lockless here. We have only two
* places which write to threads_oneshot: This code and the
* irq thread.
*
* This code is the hard irq context and can never run on two
* cpus in parallel. If it ever does we have more serious
* problems than this bitmask.
*
* The irq threads of this irq which clear their "running" bit
* in threads_oneshot are serialized via desc->lock against
* each other and they are serialized against this code by
* IRQS_INPROGRESS.
*
* Hard irq handler:
*
* spin_lock(desc->lock);
* desc->state |= IRQS_INPROGRESS;
* spin_unlock(desc->lock);
* set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
* desc->threads_oneshot |= mask;
* spin_lock(desc->lock);
* desc->state &= ~IRQS_INPROGRESS;
* spin_unlock(desc->lock);
*
* irq thread:
*
* again:
* spin_lock(desc->lock);
* if (desc->state & IRQS_INPROGRESS) {
* spin_unlock(desc->lock);
* while(desc->state & IRQS_INPROGRESS)
* cpu_relax();
* goto again;
* }
* if (!test_bit(IRQTF_RUNTHREAD, &action->thread_flags))
* desc->threads_oneshot &= ~mask;
* spin_unlock(desc->lock);
*
* So either the thread waits for us to clear IRQS_INPROGRESS
* or we are waiting in the flow handler for desc->lock to be
* released before we reach this point. The thread also checks
* IRQTF_RUNTHREAD under desc->lock. If set it leaves
* threads_oneshot untouched and runs the thread another time.
*/
//将此action的thread_mask或到desc->threads_oneshot上
//表明此中断线上的此中断线程准备开始执行
desc->threads_oneshot |= action->thread_mask;
/*
* We increment the threads_active counter in case we wake up
* the irq thread. The irq thread decrements the counter when
* it returns from the handler or in the exit path and wakes
* up waiters which are stuck in synchronize_irq() when the
* active count becomes zero. synchronize_irq() is serialized
* against this code (hard irq handler) via IRQS_INPROGRESS
* like the finalize_oneshot() code. See comment above.
*/
atomic_inc(&desc->threads_active);
wake_up_process(action->thread);//尝试唤醒该中断对应通用线程的irq_thread,TTWU
}SUM
中断线程会在 中断上下文 被TTWU唤醒,被标记为IRQTF_RUNTHREAD,之后将其加入rq。实际执行是在 进程上下文
中断线程是RT进程,优先级高,通常会被优先调度,以此确保实时性
线程化的中断在注册的时候可能会使用IRQF_ONESHOT flag,确保 执行中断线程执行函数的时候,底层的硬件中断是被屏蔽的,防止嵌套。(通常是电平中断使用这个flag,因为电平中断为了防止中断反复触发,会设置mask)
中断虽然线程化,但硬件中断还是会发生,也会走正常的异常处理流程&硬件中断处理,只是硬中断处理函数几乎不执行任何操作,或者只执行一点点操作,所有任务基本上都是交给 中断线程 处理。相当于是把原本硬件中断上下文中需要处理的绝大部分内容交给了 irq_thread内核线程