本章主要讲中断的注册过程
参考文章:
http://www.wowotech.net/linux_kenrel/request_threaded_irq.html
https://blog.youkuaiyun.com/yin262/article/details/54025216
http://blog.chinaunix.net/uid-24153750-id-5113000.html
<include/linux/interrupt.h>
static inline int __must_check
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);
}
extern 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)
/*
* 函数入参如下:
* 1) irq:虚中断号IRQ number;
* 2) handler:传统意义的中断handler,我们称之primary handler;如果handler为NULL,那么内核会为handler赋值一个default handler;
* 3) thread_fn:threaded handler。如果该参数不是NULL,那么系统会创建一个kernel thread,调用的function就是thread_fn;handler和thread_fn不能同时为空;
* 4) irqflags:描述中断特性;
* 5) devname:设备名称,显示在/proc/interrupts文件下;
* 6) dev_id:handler中会用到此参数,如果中断共享的话,该参数不能为NULL。
*/
/*
* primary handler和threaded handler参数有下面四种组合:
* primary handler | threaded handler | 描述
* NULL | NULL | 函数出错,返回-EINVAL
* 设定 | 设定 | 正常流程,中断处理被合理的分配到primary handler和threaded handler中。
* 设定 | NULL | 中断处理都是在primary handler中完成
* NULL | 设定 | 这种情况下,系统会帮忙设定一个default的primary handler:irq_default_primary_handler,协助唤醒threaded handler线程
*/
//中断特性irqflags定义如下
/*
* These flags used only by the kernel as part of the irq handling routines.
*
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend. Does not guarantee
* that this interrupt will wake the system from a suspended
* state. See Documentation/power/suspend-and-interrupts.txt
* IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
* IRQF_NO_THREAD - Interrupt cannot be threaded
* IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
* resume time.
* IRQF_COND_SUSPEND - If the IRQ is shared with a NO_SUSPEND user, execute this
* interrupt handler after suspending interrupts. For system
* wakeup devices users need to implement wakeup detection in
* their interrupt handlers.
*/
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define __IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
#define IRQF_NO_SUSPEND 0x00004000
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
#define IRQF_COND_SUSPEND 0x00040000
/*
* IRQF_SHARED:共享中断
* 这个flag用来描述一个interrupt line是否允许在多个设备中共享;
* 如果中断控制器可以支持足够多的interrupt source,那么在两个外设间共享一个interrupt request line是不推荐的,毕竟有一些额外的开销(发生中断的时候要逐个询问是不是你的中断,软件上就是遍历action list);
* 早期PC时代,使用8259中断控制器,级联的8259最多支持15个外部中断,但是PC外设那么多,因此需要irq share;
* 现在,ARM平台上的系统设计很少会采用外设共享IRQ方式,毕竟一般ARM SOC提供的有中断功能的GPIO非常的多,足够用的;
* IRQF_PERCPU
* 在SMP的架构下,中断有两种模式,一种中断是在所有processor之间共享的,也就是global的,一旦中断产生,interrupt controller可以把这个中断送达多个处理器;
* 这种中断模式下,interrupt controller针对该中断的operational register是global的,所有的CPU看到的都是一套寄存器,一旦一个CPU ack了该中断,那么其他的CPU看到的该interupt source的状态也是已经ack的状态。即,只会有一个cpu处理该中断;
* 和global对应的就是per cpu interrupt了,对于这种interrupt,不是processor之间共享的,而是特定属于一个CPU的;
* 例如GIC中interrupt ID等于30的中断就是per cpu的(这个中断event被用于各个CPU的local timer),这个中断号虽然只有一个,但是,实际上控制该interrupt ID的寄存器有n组(如果系统中有n个processor),每个CPU看到的是不同的控制寄存器;
* 在具体实现中,这些寄存器组有两种形态,一种是banked,所有CPU操作同样的寄存器地址,硬件系统会根据访问的cpu定向到不同的寄存器。另外一种是non banked,也就是说,对于该interrupt source,每个cpu都有自己独特的访问地址;
* IRQF_ONESHOT:中断处理不可打断、不可嵌套
* one shot本身的意思的只有一次的,结合到中断这个场景,则表示中断是一次性触发的,不能嵌套;
* 对于primary handler,当然是不会嵌套,但是对于threaded handler,我们有两种选择,一种是mask该interrupt source,另外一种是unmask该interrupt source;
* 一旦mask住该interrupt source,那么该interrupt source的中断在整个threaded handler处理过程中都是不会再次触发的,也就是one shot了。这种handler不需要考虑重入问题。
* IRQF_NO_THREAD:中断处理不可线程化
* 有些low level的interrupt是不能线程化的(例如系统timer的中断),这个flag就是起这个作用的。另外,有些级联的interrupt controller对应的IRQ也是不能线程化的(例如secondary GIC对应的IRQ)。
*/
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
/*
* Linux内核中的中断子系统有四种状态、特性,分别是:
*
* 1)用于描述irq_desc的istate。 // 前缀为IRQS_
* 2)用于描述irq_desc->action->handler的flags。 // 前缀为IRQF_
* 3)用于描述irq_desc->action->thread_fn的thread_flags。// 前缀为IRQTF_
* 4)用于描述irq_desc->irq_data的state_use_accessors。 // 前缀为IRQD_
*/
<kernel/irq/internals.h>
#define istate core_internal_state__do_not_mess_with_it
/*
* Bits used by threaded handlers:
* IRQTF_RUNTHREAD - signals that the interrupt handler thread should run
* IRQTF_WARNED - warning "IRQ_WAKE_THREAD w/o thread_fn" has been printed
* IRQTF_AFFINITY - irq thread is requested to adjust affinity
* IRQTF_FORCED_THREAD - irq action is force threaded
*/
enum {
IRQTF_RUNTHREAD,
IRQTF_WARNED,
IRQTF_AFFINITY,
IRQTF_FORCED_THREAD,
};
/*
* Bit masks for desc->core_internal_state__do_not_mess_with_it
*
* IRQS_AUTODETECT - autodetection in progress
* IRQS_SPURIOUS_DISABLED - was disabled due to spurious interrupt detection
* IRQS_POLL_INPROGRESS - polling in progress
* IRQS_ONESHOT - irq is not unmasked in primary handler
* IRQS_REPLAY - irq is replayed
* IRQS_WAITING - irq is waiting
* IRQS_PENDING - irq is pending and replayed later
* IRQS_SUSPENDED - irq is suspended
*/
enum {
IRQS_AUTODETECT = 0x00000001,
IRQS_SPURIOUS_DISABLED = 0x00000002,
IRQS_POLL_INPROGRESS = 0x00000008,
IRQS_ONESHOT = 0x00000020,
IRQS_REPLAY = 0x00000040,
IRQS_WAITING = 0x00000080,
IRQS_PENDING = 0x00000200,
IRQS_SUSPENDED = 0x00000800,
};
<include/linux/irq.h>
/*
* Bit masks for irq_common_data.state_use_accessors
*
* IRQD_TRIGGER_MASK - Mask for the trigger type bits
* IRQD_SETAFFINITY_PENDING - Affinity setting is pending
* IRQD_NO_BALANCING - Balancing disabled for this IRQ
* IRQD_PER_CPU - Interrupt is per cpu
* IRQD_AFFINITY_SET - Interrupt affinity was set
* IRQD_LEVEL - Interrupt is level triggered
* IRQD_WAKEUP_STATE - Interrupt is configured for wakeup from suspend
* IRDQ_MOVE_PCNTXT - Interrupt can be moved in process context
* IRQD_IRQ_DISABLED - Disabled state of the interrupt
* IRQD_IRQ_MASKED - Masked state of the interrupt
* IRQD_IRQ_INPROGRESS - In progress state of the interrupt
* IRQD_WAKEUP_ARMED - Wakeup mode armed
* IRQD_FORWARDED_TO_VCPU - The interrupt is forwarded to a VCPU
*/
enum {
IRQD_TRIGGER_MASK = 0xf,
IRQD_SETAFFINITY_PENDING= (1 << 8),
IRQD_NO_BALANCING = (1 << 10),
IRQD_PER_CPU = (1 << 11),
IRQD_AFFINITY_SET = (1 << 12),
IRQD_LEVEL = (1 << 13),
IRQD_WAKEUP_STATE = (1 << 14),
IRQD_MOVE_PCNTXT = (1 << 15),
IRQD_IRQ_DISABLED = (1 << 16),
IRQD_IRQ_MASKED = (1 << 17),
IRQD_IRQ_INPROGRESS = (1 << 18),
IRQD_WAKEUP_ARMED = (1 << 19),
IRQD_FORWARDED_TO_VCPU = (1 << 20),
};
/*
* IRQ line status.
*
* Bits 0-7 are the same as the IRQF_* bits in linux/interrupt.h
*
* IRQ_TYPE_NONE - default, unspecified type
* IRQ_TYPE_EDGE_RISING - rising edge triggered
* IRQ_TYPE_EDGE_FALLING - falling edge triggered
* IRQ_TYPE_EDGE_BOTH - rising and falling edge triggered
* IRQ_TYPE_LEVEL_HIGH - high level triggered
* IRQ_TYPE_LEVEL_LOW - low level triggered
* IRQ_TYPE_LEVEL_MASK - Mask to filter out the level bits
* IRQ_TYPE_SENSE_MASK - Mask for all the above bits
* IRQ_TYPE_DEFAULT - For use by some PICs to ask irq_set_type
* to setup the HW to a sane default (used
* by irqdomain map() callbacks to synchronize
* the HW state and SW flags for a newly
* allocated descriptor).
*
* IRQ_TYPE_PROBE - Special flag for probing in progress
*
* Bits which can be modified via irq_set/clear/modify_status_flags()
* IRQ_LEVEL - Interrupt is level type. Will be also updated in the code when the above trigger bits are modified via irq_set_irq_type()
* IRQ_PER_CPU - Mark an interrupt PER_CPU. Will protect it from affinity setting
* IRQ_NOPROBE - Interrupt cannot be probed by autoprobing
* IRQ_NOREQUEST - Interrupt cannot be requested via request_irq()
* IRQ_NOTHREAD - Interrupt cannot be threaded
* IRQ_NOAUTOEN - Interrupt is not automatically enabled in request/setup_irq()
* IRQ_NO_BALANCING - Interrupt cannot be balanced (affinity set)
* IRQ_MOVE_PCNTXT - Interrupt can be migrated from process context
* IRQ_NESTED_THREAD - Interrupt nests into another thread
* IRQ_PER_CPU_DEVID - Dev_id is a per-cpu variable
* IRQ_IS_POLLED - Always polled by another interrupt. Exclude it from the spurious interrupt detection mechanism and from core side polling.
* IRQ_DISABLE_UNLAZY - Disable lazy irq disable
*/
enum {
IRQ_TYPE_NONE = 0x00000000,
IRQ_TYPE_EDGE_RISING = 0x00000001,
IRQ_TYPE_EDGE_FALLING = 0x00000002,
IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
IRQ_TYPE_LEVEL_HIGH = 0x00000004,
IRQ_TYPE_LEVEL_LOW = 0x00000008,
IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH),
IRQ_TYPE_SENSE_MASK = 0x0000000f,
IRQ_TYPE_DEFAULT = IRQ_TYPE_SENSE_MASK,
IRQ_TYPE_PROBE = 0x00000010,
IRQ_LEVEL = (1 << 8),
IRQ_PER_CPU = (1 << 9),
IRQ_NOPROBE = (1 << 10),
IRQ_NOREQUEST = (1 << 11),
IRQ_NOAUTOEN = (1 << 12),
IRQ_NO_BALANCING = (1 << 13),
IRQ_MOVE_PCNTXT = (1 << 14),
IRQ_NESTED_THREAD = (1 << 15),
IRQ_NOTHREAD = (1 << 16),
IRQ_PER_CPU_DEVID = (1 << 17),
IRQ_IS_POLLED = (1 << 18),
IRQ_DISABLE_UNLAZY = (1 << 19),
};
<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;
/*
* 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 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_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL;
desc = irq_to_desc(irq); /* 由虚中断号获取对应中断描述符 */
if (!desc)
return -EINVAL;
/* 有些中断描述符被标记为IRQ_NOREQUEST,标识该IRQ number不能被其他的驱动request。
* 一般而言,这些IRQ number有特殊的作用,例如用于级联的那个IRQ number是不能request。
* irq_settings_can_request函数就是判断一个IRQ是否可以被request。
* */
if (!irq_settings_can_request(desc)
/* irq_settings_is_per_cpu_devid函数用来判断一个中断描述符是否需要传递percpu的device ID;
* 如果一个中断描述符对应的中断 ID是percpu的,那么在申请其handler的时候就有两种情况:
* 一种是传递统一的dev_id参数(传入request_threaded_irq的最后一个参数);
* 一种情况是针对每个CPU,传递不同的dev_id参数。在这种情况下,我们需要调用request_percpu_irq接口函数而不是request_threaded_irq。
* */
|| WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
if (!handler) { /* handler和thread_fn不能同时为空 */
if (!thread_fn) /* 如果handler为空,则赋值为irq_default_primary_handler */
return -EINVAL;
handler = irq_default_primary_handler; /* 定义如下 */
}
/* 分配struct irqaction,赋值,调用__setup_irq进行实际的注册过程 */
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;
/* 大部分的interrupt controller并没有定义irq_bus_lock这个callback函数;
* 因此chip_bus_lock这个函数对大多数的中断控制器而言是没有实际意义的;
* 但是,有些interrupt controller是连接到慢速总线上的,例如一个i2c接口的IO expander芯片,
* 在访问这种interrupt controller的时候需要lock住那个慢速bus(只能有一个client在使用I2C bus)。
* */
chip_bus_lock(desc);
retval = __setup_irq(irq, desc, action); /* 核心函数 */
chip_bus_sync_unlock(desc);
if (retval) {
kfree(action->secondary);
kfree(action);
}
......
return retval;
}
=========================================================================================
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;
cpumask_var_t mask;
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_NESTED_THREAD,判断是否是嵌套中断线程,关于中断嵌套的处理,在后续有分析 */
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) { /* 嵌套中断不需要有primary handler,但是一定要有thread_fn */
ret = -EINVAL;
goto out_mput;
}
/* 如果一个中断描述符是nested thread type的,说明这个中断描述符应该设定threaded interrupt handler;
* 内核是不会单独创建一个thread的,它是借着其parent IRQ的interrupt thread执行;
* 对于primary handler,它应该没有机会被调用到,当然为了调试,kernel将其设定为irq_nested_primary_handler,以便在调用的时候打印一些信息;
* */
new->handler = irq_nested_primary_handler;
}
else {
/* forced irq threading其实就是将系统中所有可以被线程化的中断handler全部线程化,
* 即便你在request irq的时候,设定的是primary handler,而不是threaded handler。
* 当然那些不能被线程化的中断(标注了IRQF_NO_THREAD的中断,例如系统timer)还是排除在外的。
* irq_settings_can_thread函数就是判断一个中断是否可以被线程化,
* 如果可以的话,则调用irq_setup_forced_threading在set irq的时候强制进行线程化。
* */
if (irq_settings_can_thread(desc)) {
ret = irq_setup_forced_threading(new); /* 详细分析见下方 */
if (ret)
goto out_mput;
}
}
/* 创建中断处理内核线程,设置了内核线程优先级和调度策略 */
if (new->thread_fn && !nested) {
ret = setup_irq_thread(new, irq, false); /* 详细分析如下 */
if (ret)
goto out_mput;
if (new->secondary) {
ret = setup_irq_thread(new->secondary, irq, true);
if (ret)
goto out_thread;
}
}
/* 分配一个cpu mask的变量的内存,用于后面的set_affinity mask */
if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {
ret = -ENOMEM;
goto out_thread;
}
/*
* 有些interrupt controller(例如MSI based interrupt)本质上就是就是one shot的(通过IRQCHIP_ONESHOT_SAFE标记);
* 因此驱动工程师设定的IRQF_ONESHOT其实是画蛇添足,可以去掉。
*/
if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
new->flags &= ~IRQF_ONESHOT;
/*
* The following block of code has to be executed atomically
*/
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.
*/
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||
((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;
}
/*
* 对于one shot类型的中断,我们还需要设定thread mask。
* 如果一个one shot类型的中断只有一个threaded handler(不支持共享),那么事情就很简单(临时变量thread_mask等于0),该irqaction的thread_mask成员总是使用第一个bit来标识该irqaction。
* 如果支持共享的话,事情变得有点复杂。我们假设这个one shot类型的IRQ上有A,B和C三个irqaction,那么A,B和C三个irqaction的thread_mask成员会有不同的bit来标识自己。
* 疑问:如果设置了primary handler和IRQF_ONESHOT,也会走到这个分支?
*/
if (new->flags & IRQF_ONESHOT) {
/*
* 在上面“共享中断的处理”代码中,thread_mask变量保存了所有的属于该interrupt line的thread_mask;
* 如果thread_mask变量如果是全1,那么说明irqaction list上已经有了太多的irq action(大于32或者64,和具体系统和编译器相关)。
* 如果没有满,那么通过ffz函数找到第一个为0的bit作为该irq action的thread bit mask。
*/
if (thread_mask == ~0UL) {
ret = -EBUSY;
goto out_mask;
}
/*
* ffz - find first zero bit in word
* @word: The word to search
*
* Undefined if no zero exists, so code should check against ~0UL first.
*/
new->thread_mask = 1 << ffz(thread_mask);
} else if (new->handler == irq_default_primary_handler &&
!(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
/*
* irq_default_primary_handler虽然简单,但是有一个风险:如果是电平触发的中断,我们需要操作外设的寄存器才可以让那个asserted的电平信号消失,否则它会一直持续。
* 一般,我们都是直接在primary中操作外设寄存器(slow bus类型的interrupt controller不行),尽早的clear interrupt,
* 但是,对于irq_default_primary_handler,它仅仅是wakeup了threaded interrupt handler,并没有clear interrupt,
* 这样,执行完了primary handler,外设中断仍然是asserted,一旦打开CPU中断,立刻触发下一次的中断,然后不断的循环。
* 因此,如果注册中断的时候没有指定primary interrupt handler,并且没有设定IRQF_ONESHOT,那么系统是会报错的。
* 当然,有一种情况可以豁免,当底层的irq chip是one shot safe的(IRQCHIP_ONESHOT_SAFE)。
*/
pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n", irq);
ret = -EINVAL;
goto out_mask;
}
/* 非共享中断 */
if (!shared) {
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_mask;
}
/* 初始化等待队列 */
init_waitqueue_head(&desc->wait_for_threads);
/* Setup the type (level, edge polarity) if configured:
* 设置中断的触发模式
*/
if (new->flags & IRQF_TRIGGER_MASK) {
ret = __irq_set_trigger(desc, new->flags & IRQF_TRIGGER_MASK);
if (ret)
goto out_mask;
}
/* 清除状态标志位 */
desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | IRQS_ONESHOT | IRQS_WAITING);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
/* 标志位的同步 */
if (new->flags & IRQF_PERCPU) {
irqd_set(&desc->irq_data, IRQD_PER_CPU);
irq_settings_set_per_cpu(desc);
}
if (new->flags & IRQF_ONESHOT)
desc->istate |= IRQS_ONESHOT;
/* Interrupt is automatically enabled in request/setup_irq() */
if (irq_settings_can_autoenable(desc))
irq_startup(desc, true);
else
/* Undo nested disables: */
desc->depth = 1;
/* Exclude IRQ from balancing if requested */
if (new->flags & IRQF_NOBALANCING) {
irq_settings_set_no_balancing(desc);
irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
}
/* Set default affinity mask once everything is setup */
setup_affinity(desc, mask);
} else if (new->flags & IRQF_TRIGGER_MASK) {
unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;
unsigned int omsk = irq_settings_get_trigger_mask(desc);
if (nmsk != omsk)
/* hope the handler works with current trigger mode */
pr_warning("irq %d uses trigger mode %u; requested %u\n", irq, nmsk, omsk);
}
*old_ptr = new; /* 此处将new挂接至desc */
irq_pm_install_action(desc, new); /* 函数定义如下 */
/* Reset broken irq detection when installing new handler */
desc->irq_count = 0;
desc->irqs_unhandled = 0;
/*
* Check whether we disabled the irq via the spurious handler
* before. Reenable it and give it another chance.
*/
if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {
desc->istate &= ~IRQS_SPURIOUS_DISABLED;
__enable_irq(desc);
}
raw_spin_unlock_irqrestore(&desc->lock, flags);
/*
* Strictly no need to wake it up, but hung_task complains
* when no hard interrupt wakes the thread up.
*/
/* 唤醒之前创建的中断处理内核线程
* 疑问:还没有中断,为什么此处要唤醒线程化中断处理的内核线程?
* handle_irq_event中会唤醒中断处理内核线程
* */
if (new->thread)
wake_up_process(new->thread);
if (new->secondary)
wake_up_process(new->secondary->thread);
/* proc进程目录下创建对应文件 */
register_irq_proc(irq, desc);
new->dir = NULL;
register_handler_proc(irq, new);
free_cpumask_var(mask);
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_mask:
raw_spin_unlock_irqrestore(&desc->lock, flags);
free_cpumask_var(mask);
out_thread:
if (new->thread) {
struct task_struct *t = new->thread;
new->thread = NULL;
kthread_stop(t);
put_task_struct(t);
}
if (new->secondary && new->secondary->thread) {
struct task_struct *t = new->secondary->thread;
new->secondary->thread = NULL;
kthread_stop(t);
put_task_struct(t);
}
out_mput:
module_put(desc->owner);
return ret;
}
==========================================================================================
/* 中断处理强制线程化 */
static int irq_setup_forced_threading(struct irqaction *new)
{
/* 内核配置选项CONFIG_IRQ_FORCED_THREADING,如果没有打开该选项,force_irqthreads总是0;
* 如果打开了CONFIG_IRQ_FORCED_THREADING,说明系统支持强制线程化,但是具体是否对所有的中断进行强制线程化处理还是要看命令行参数threadirqs。
* 如果kernel启动的时候没有传入该参数,那么同样的,irq_setup_forced_threading也就没有什么作用,直接return了。
* 只有bootloader向内核传入threadirqs这个命令行参数,内核才真正在启动过程中,进行各个中断的强制线程化的操作。
* */
if (!force_irqthreads)
return 0;
/* PERCPU的中断都是一些较为特殊的中断,不是一般意义上的外设中断,因此对PERCPU的中断不强制进行线程化。
* IRQF_ONESHOT选项说明该中断已经被线程化了(而且是特殊的one shot类型的),因此也是直接返回了。
* IRQF_ONESHOT属性的handler是不可抢占、不可嵌套的,不强制进行进行线程化;
* */
if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
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.
*/
/* 对于primary handler和thread_fn都存在的情况下,创建new->secondary */
if (new->handler != irq_default_primary_handler && new->thread_fn) {
/* Allocate the secondary action */
new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!new->secondary)
return -ENOMEM;
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;
}
========================================================================================
/* 创建中断处理内核线程 */
static int setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
struct task_struct *t;
struct sched_param param = {
.sched_priority = MAX_USER_RT_PRIO/2, /* #define MAX_USER_RT_PRIO 100 */
};
/*
* 调用kthread_create来创建中断内核线程;
* 线程创建后,不会马上运行,而是需要将kthread_create()返回的task_struct指针传给wake_up_process(),然后通过此函数运行线程。
* kthread_run:创建并启动线程;
* */
if (!secondary) {
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);
param.sched_priority -= 1;
}
if (IS_ERR(t))
return PTR_ERR(t);
/* 设定中断内核线程的调度策略和优先级 */
sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);
/*
* 调用get_task_struct可以为这个threaded handler的task struct增加一次reference count,
* 这样,即便是该thread异常退出也可以保证它的task struct不会被释放掉。这可以保证中断系统的代码不会访问到一些被释放的内存。
* irqaction的thread成员被设定为刚刚创建的task,这样,primary handler就知道唤醒哪一个中断线程了。
*/
get_task_struct(t);
new->thread = t;
/*
* 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.
*/
set_bit(IRQTF_AFFINITY, &new->thread_flags);
return 0;
}
======================================================================================
/*
* Default primary interrupt handler for threaded interrupts. Is
* assigned as primary handler when request_threaded_irq is called
* with handler == NULL. Useful for oneshot interrupts.
*/
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}
<kernel/irq/settings.h>
/*
* Internal header to deal with irq_desc->status which will be renamed
* to irq_desc->settings.
*/
enum {
_IRQ_DEFAULT_INIT_FLAGS = IRQ_DEFAULT_INIT_FLAGS,
_IRQ_PER_CPU = IRQ_PER_CPU,
_IRQ_LEVEL = IRQ_LEVEL,
_IRQ_NOPROBE = IRQ_NOPROBE,
_IRQ_NOREQUEST = IRQ_NOREQUEST,
_IRQ_NOTHREAD = IRQ_NOTHREAD,
_IRQ_NOAUTOEN = IRQ_NOAUTOEN,
_IRQ_MOVE_PCNTXT = IRQ_MOVE_PCNTXT,
_IRQ_NO_BALANCING = IRQ_NO_BALANCING,
_IRQ_NESTED_THREAD = IRQ_NESTED_THREAD,
_IRQ_PER_CPU_DEVID = IRQ_PER_CPU_DEVID,
_IRQ_IS_POLLED = IRQ_IS_POLLED,
_IRQ_DISABLE_UNLAZY = IRQ_DISABLE_UNLAZY,
_IRQF_MODIFY_MASK = IRQF_MODIFY_MASK,
};
static inline bool irq_settings_can_request(struct irq_desc *desc)
{
return !(desc->status_use_accessors & _IRQ_NOREQUEST);
}
<kernel/irq/pm.c>
/*
* Called from __setup_irq() with desc->lock held after @action has
* been installed in the action chain.
*/
void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action)
{
desc->nr_actions++;
if (action->flags & IRQF_FORCE_RESUME)
desc->force_resume_depth++;
WARN_ON_ONCE(desc->force_resume_depth &&
desc->force_resume_depth != desc->nr_actions);
if (action->flags & IRQF_NO_SUSPEND)
desc->no_suspend_depth++;
else if (action->flags & IRQF_COND_SUSPEND)
desc->cond_suspend_depth++;
WARN_ON_ONCE(desc->no_suspend_depth &&
(desc->no_suspend_depth + desc->cond_suspend_depth) != desc->nr_actions);
}
=======================================================================================
/*
* Called from __free_irq() with desc->lock held after @action has
* been removed from the action chain.
*/
void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action)
{
desc->nr_actions--;
if (action->flags & IRQF_FORCE_RESUME)
desc->force_resume_depth--;
if (action->flags & IRQF_NO_SUSPEND)
desc->no_suspend_depth--;
else if (action->flags & IRQF_COND_SUSPEND)
desc->cond_suspend_depth--;
}