linux驱动移植-中断注册
在之前我们介绍了linux中断子系统向驱动程序提供了注册中断的API:
-
request_threaded_irq;
-
request_irq;
这一节我们将从源码层面介绍讲解它们的实现。
一、cascade IRQ和nested IRQ
linux中断子系统在注册中断的时候,根据中断控制器的结构实际上对中断进行了分类,以适配不同的硬件情景,比如cascade IRQ和nested IRQ。
1.1 cascade IRQ
上面是一个两个VIC级联的例子,我们可以把VIC0当做S3C2440的中断控制器,VIC1假设成其外部中断。为了方便描述:
- VIC1的IRQ编号是EINT4~7;
- 外设1的IRQ编号是EINT4;
- 外设2的IRQ编号是EINT5;
- 外设3的IRQ编号是EINT6;
- 外设4的IRQ编号是EINT7;
对于级联的中断控制器,外侧的中断控制器不能直接向ARM内核递交请求,但它可以向与之相连的内存的中断控制器请求服务。
当外设1触发中断时,中断信号会递交给VIC1、然后VIC1会将中断信号递交给VIC0,然后再递交给ARM内核。因此首先会执行EINT4~7中断描述符desc->handle_irq方法,也就是s3c_irq_demux。
static void s3c_irq_demux(struct irq_desc *desc) { struct irq_chip *chip = irq_desc_get_chip(desc); struct s3c_irq_data *irq_data = irq_desc_get_chip_data(desc); struct s3c_irq_intc *intc = irq_data->intc; struct s3c_irq_intc *sub_intc = irq_data->sub_intc; unsigned int n, offset, irq; unsigned long src, msk; /* we're using individual domains for the non-dt case * and one big domain for the dt case where the subintc * starts at hwirq number 32. */ offset = irq_domain_get_of_node(intc->domain) ? 32 : 0; chained_irq_enter(chip, desc); src = readl_relaxed(sub_intc->reg_pending); msk = readl_relaxed(sub_intc->reg_mask); src &= ~msk; src &= irq_data->sub_bits; while (src) { n = __ffs(src); src &= ~(1 << n); irq = irq_find_mapping(sub_intc->domain, offset + n); generic_handle_irq(irq); } chained_irq_exit(chip, desc); }
然后再s3c_irq_demux会根据EINTPEND寄存器判断出发生的是哪一个外部中断,然后根据中断域调用irq_find_mapping获取到外部中断的IRQ编号,最后通过generic_handle_irq调用外部中断注册中断处理函数。
通过上面的分析我们可以总结出:
- 主中断EINT4~7的中断描述符被设定为不能注册irqaction(因为就算注册,也始终不会执行中断描述符的irqaction链表);
- EINT4~EINT7外部中断正常进行中断注册,通过request_threaded_irq注册中断,可以选择thread interrupt handler,或者只注册primary handler;
linux内核将EINT4、EINT5、EINT6、EINT7这种中断称为cascade IRQ。
1.2 nested IRQ
我们再来看一下另一种中断控制器级联的情况:
IO expander模块提供了有中断功能的GPIO,因此它也可以看做一个中断控制器,有它自己的irq domain和irq chip。
上图外设1和外设2使用了IO expander上有中断功能的GPIO,它们有属于自己的IRQ编号以及中断描述符,IO expander模块的IRQ line链接到SOC内部的中断控制器上,因此这也是一个中断控制器级联的情况,对于这种情况,和我们上面介绍的VIC级联的情况有何区别。
对于VIC级联的情况,如果VIC1上的外设1产生了中断,将会执行中断描述符的handle_irq,以handle_level_irq流控处理函数为例,其在执行开始会关闭当前中断,整个关中断的时间=eint_4t7_desc->handle_irq + eint4->handle + eint4->action执行时间。由于VIC0、VIC1寄存器访问速度很快,整个关中断的时间不是很长。
但是如果是IO expander这种情况,如果采用和VIC级联一样的处理方式,关中断的时间会非常长。这时候,由于外设1的的中断描述符的handle_irq 处理涉及I2C的操作,因此时间非常的长,这时候,对于整个系统的实时性而言是致命的打击。
为了方便描述:
- VIC的IRQ编号是A;
- 外设1的IRQ编号是B;
- 外设2的IRQ编号是C;
对这种硬件情况,linux kernel处理如下:
- IRQ A的中断描述符的handle_irq 根据实际情况进行设定,并且允许注册irqaction;
- 在IRQ A的中断描述符的threaded interrupt handler中进行IRQ number的映射,在IO expander irq domain上翻译出具体外设的IRQ number,并直接调用handle_nested_irq函数处理该IRQ;
- IRQ B、IRQ C对应的中断描述符设定IRQ_NESTED_THREAD的flag,表明这是一个nested IRQ;
- nested IRQ没有handle_irq,也没有primary handler,它的threaded interrupt handler是附着在其parent IRQ的threaded interrupt handler上的;
linux内核将IRQ B、IRQ C这种中断称为nested IRQ。这里的 nested 并不是中断嵌套,而是针对某一类特殊的中断控制器做兼容。
二、中断注册
2.1 ISR的安装
我们在编写设备驱动程序时,如果需要使用到某个中断,那么我们就需要在中断到来之前注册该中断的处理处理函数,也就是ISR - Interrupt Service Routine。
由于早期处理器,存在IRQ编号共享问题,因此Linux的中断子系统是按照服务中断共享的模式设计的,因此ISR的安装需要分为两级:
- 第一级是针对这个IRQ线的,称为generic handler(或者叫做highlevel irq-events handler),也就是中断描述符的handler_irq(desc->handle_irq);
- 第二级是针对挂载在这个IRQ线上的不同设备的,称为specific handler,也就是中断描述符的irqaction链表(desc->action);
上一节我们分析了S3C2440中断相关的代码,Generic handler的初始化通过s3c24xx_irq_map中的irq_set_chip_and_handler函数实现的。
Specific handler的安装则是由request_threaded_irq函数完成的。request_threaded_irq可以选择线程化的handler,或者只注册primary handler。
2.2 中断线程化(threaded interrupt handler)
在linux中,中断具有最高优先级,无论什么时刻,只要有中断产生,内核将会立即执行相应的中断处理处理,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此可能造成实时任务得不到及时的处理。
中断线程化之后,中断将作为内核线程运行而被赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级。这样,具有最高优先级的实时任务就能得到优先处理,即使在严重负载下仍有实时性保证。但是,并不是所有的中断都可以线程化,比如时钟中断,主要用来维护系统时间以及定时器等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当被线程化。
中断线程化有一个内核配置选项是 CONFIG_IRQ_FORCED_THREADING,这个内核选项决定了系统中的中断是不是会被强制线程化,如果该选项配置为 Y,意味着注册的中断默认都会创建一个线程。
2.3 request_threaded_irq函数原型
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)
该函数参数:
- irq:中断号,所申请的中断向量;
- handler:中断处理服务函数 primary handler,该函数工作在中断上下文中,如果不需要,可以传入NULL,但是不可以和thread_fn同时为NULL;
- thread_fn:中断线程化的处理函数 threaded interrupt handler,工作在内核进程上下文中,如果不需要,可以传入NULL,但是不可以和handler同时为NULL;如果中断处理函数的返回值是IRQ_WAKE_THREAD,则此时注册的中断线程处理函数将被调用,此函数是对中断处理函数的补充;
- flags:设置中断类型标志位,定义在include/linux/interrupt.h中;如果为0,则使用默认中断类型,比如从设备节点interrupts属性解析到的中断触发类型;
- name:指定中断的名称;用命令cat /proc/interrupts可查看系统中断申请与使用情况;
- dev:传入中断处理程序的参数,可以为NULL,但在注册共享中断时,此参数不能为NULL。该参数可作为共享中断时的中断区别参数,还可以把其传给一个结构体变量,用于保存一个设备的信息,使中断处理函数可以获得该设备的信息;
注:传入request_threaded_irq的handler和thread_fn参数有下面四种组合:
handler | thread_fn | 描述 |
NULL | NULL | 函数出错,返回-EINVAL |
设定 | 设定 | 正常流程。中断处理被合理的分配到primary handler和threaded interrupt handler中。 |
设定 | NULL | 中断处理都是在primary handler中完成,等同于request_irq() |
NULL | 设定 | 这种情况下,handler=irq_default_primary_handler,协助唤醒threaded interrupt handler线程 |
如果返回值是0则说明申请成功,如果申请不成功,则返回的值非零,一般为负数,可能的取值为-16、-38。例如,如果返回值是-16,则说明申请的中断号在内核中已被占用。
参数flags可能取值如下,该函数需要根据硬件中断的类型来决定设置哪些标志位:
/* * These correspond to the IORESOURCE_IRQ_* defines in * linux/ioport.h to select the interrupt line behaviour. When * requesting an interrupt without specifying a IRQF_TRIGGER, the * setting should be assumed to be "as already configured", which * may be as per machine or firmware initialisation. */ #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 /* * 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 a 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 // 共享中断标志位,多个设备共享一个中IRQ编号时,设置该标志 #define IRQF_PROBE_SHARED 0x00000100 // 共享中断允许不匹配发生时设置该标志位 #define __IRQF_TIMER 0x00000200 // 时钟中断标志位 #define IRQF_PERCPU 0x00000400 // 属于某个特定CPU的中断 #define IRQF_NOBALANCING 0x00000800 // 禁止在多CPU之间做中断均衡 #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 #define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
2.4 request_threaded_irq函数实现
/** * 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 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_f. 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 * */ 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 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); // 根据IRQ编号获取中断描述符 if (!desc) return -EINVAL; if (!irq_settings_can_request(desc) || // 判断一个IRQ是否可以被request,标识位IRQ_NOREQUEST的中断不能为请求 WARN_ON(irq_settings_is_per_cpu_devid(desc))) return -EINVAL; if (!handler) { if (!thread_fn) return -EINVAL; handler = irq_default_primary_handler; // 设置默认primary_handler,irq_default_primary_handler默认返回IRQ_WAKE_THREAD } action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); // 分配irqaction结构 if (!action) return -ENOMEM; action->handler = handler; action->thread_fn = 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); } return retval; }
主要实现以下功能:
- 首先调用irq_to_desc根据IRQ编号获取中断描述符desc;
- 然后分配一个irqaction结构,用参数handler,thread_fn,irqflags,devname,dev_id初始化irqaction结构的各字段;
- 最后把大部分工作委托给__setup_irq函数:
2.5 __setup_irq
__setup_irq函数也是定义在kernel/irq/manage。c文件中:
/* * 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; /* * 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. */ 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 { if (irq_settings_can_thread(desc)) { 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) { 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; } } /* * 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. */ 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. */ 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 (desc->istate & IRQS_NMI) { 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)) || ((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) { /* * Unlikely to have 32 resp 64 irqs sharing one line, * but who knows. */ if (thread_mask == ~0UL) { ret = -EBUSY; goto out_unlock; } /* * The thread_mask for the action is or'ed to * desc->thread_active to indicate that the * IRQF_ONESHOT thread handler has been woken, but not * yet finished. The bit is cleared when a thread * completes. When all threads of a shared interrupt * line have completed desc->threads_active becomes * zero and the interrupt line is unmasked. See * handle.c:irq_wake_thread() for further information. * * If no thread is woken by primary (hard irq context) * interrupt handlers, then desc->threads_active is * also checked for zero to unmask the irq line in the * affected hard irq flow handlers * (handle_[fasteoi|level]_irq). * * The new action gets the first zero bit of * thread_mask assigned. See the loop above which or's * all existing action->thread_mask bits. */ 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 irq %d\n", irq); ret = -EINVAL; goto out_unlock; } if (!shared) { 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_unlock; } /* * Activate the interrupt. That activation must happen * independently of IRQ_NOAUTOEN. request_irq() can fail * and the callers are supposed to handle * that. enable_irq() of an interrupt requested with * IRQ_NOAUTOEN is not supposed to fail. The activation * keeps it in shutdown mode, it merily associates * resources if necessary and if that's not possible it * fails. Interrupts which are in managed shutdown mode * will simply ignore that activation request. */ ret = irq_activate(desc); if (ret) goto out_unlock; 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; /* 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); } if (irq_settings_can_autoenable(desc)) { irq_startup(desc, IRQ_RESEND, IRQ_START_COND); } else { /* * 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. */ WARN_ON_ONCE(new->flags & IRQF_SHARED); /* Undo nested disables: */ desc->depth = 1; } } else if (new->flags & IRQF_TRIGGER_MASK) { unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK; unsigned int omsk = irqd_get_trigger_type(&desc->irq_data); if (nmsk != omsk) /* hope the handler works with current trigger mode */ pr_warn("irq %d uses trigger mode %u; requested %u\n", irq, omsk, nmsk); } *old_ptr = new; 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); chip_bus_sync_unlock(desc); mutex_unlock(&desc->request_mutex); irq_setup_timings(desc, new); /* * Strictly no need to wake it up, but hung_task complains * when no hard interrupt wakes the thread up. */ if (new->thread) wake_up_process(new->thread); if (new->secondary) wake_up_process(new->secondary->thread); 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(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; }
__setup_irq()首先做参数检查,然后根据需要创建中断内核线程,这期间处理nested irq、oneshot、中断共享等问题。该函数实现代码实在太长了,下面我们具体介绍。
三、__setup_irq
3.1 中断触发类型
如果没有设置中断触发类型,则使用默认值;
/* * 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);
3.1.1 irqd_get_trigger_type
irqd_get_trigger_type函数定义在include/linux/irq.h:
#define __irqd_to_state(d) ACCESS_PRIVATE((d)->common, state_use_accessors) static inline u32 irqd_get_trigger_type(struct irq_data *d) { return __irqd_to_state(d) & IRQD_TRIGGER_MASK; }
irq_common_data.state_use_accessors低4位表示中断触发类型,因此这里通过&操作获取中断触发类型。
3.2 nested irq
/* * Check whether the interrupt nests into another interrupt * thread. */ nested = irq_settings_is_nested_thread(desc); // 对于设置了IRQ_NESTED_THREAD类型的中断描述符,必指定定thread_fn if (nested) { if (!new->thread_fn) { // nested irq不需要handler,但是需要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; // 设置默认primary handler } else { if (irq_settings_can_thread(desc)) { // 判断当前中断是否可以被线程化 ret = irq_setup_forced_threading(new); // 强制中断线程化 if (ret) goto out_mput; } }
这段代码分析如下:
- 在中断子系统初始化的时候,如果存在这种非常特殊的中断控制器,就会在相关的 irq desc中设置相应的IRQ_NESTED_THREAD标志,因此 irq_settings_is_nested_thread 函数其实是读取 desc 中的相关标志以确定当前中断是不是 nested 中断;
-
如果IRQ为nested irq,需要提供 thread_fn,同时将 handler 设置为一个无效的 handle,该 handle 什么都不做,只是返回 IRQ_NONE;
-
如果IRQ不是nested irq,如果强制中断线程化CONFIG_IRQ_FORCED_THREADING被设置,调用 irq_setup_forced_threading 函数:
3.2.1 irq_setup_forced_threading
irq_setup_forced_threading 函数定义如下:
static int irq_setup_forced_threading(struct irqaction *new) { if (!force_irqthreads) return 0; if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)) // 1 return 0; new->flags |= IRQF_ONESHOT; if (new->handler != irq_default_primary_handler && new->thread_fn) { // 2 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; } set_bit(IRQTF_FORCED_THREAD, &new->thread_flags); new->thread_fn = new->handler; new->handler = irq_default_primary_handler; return 0; }
注1:内核配置强制中断线程化只是一个默认的配置,如果用户决定不使用中断线程化,还是得尊重用户的决定,有三种情况是不需要强制线程化的:
- IRQF_NO_THREAD 很好理解,就是直接指定不创建内核线程。;
- IRQF_PERCPU 中断绑定到每个 CPU 的,这种中断并不大适合线程化,假设一个 8 核的系统,每一个 percpu 的中断线程化就得创建 8 个内核线程,明显得不偿失;
- IRQF_ONESHOT 这个标志表示在中断处理程序完全处理完之后该中断都是关闭的;
对于强制线程化的中断而言,需要设置 ONESHOT 标志,也就是原本带有 ONESHOT 标志的中断不强制线程化,而强制线程化的在线程化过程中需要加上 ONESHOT 标志, 对于实际的处理来说,ONESHOT 类型的中断处理就是在处理器 mask 中断,用户处理完成之后再 unmask 中断。
注2:对于强制线程化的中断而言,如果用户注册时使用 request_threaded_irq,同时给出了 handler 和 thread_fn,这种情况比较特殊,说明注册者既希望在中断上下文中处理一部分,又希望在内核线程中处理一部分,这时候使用了 action 中的以新的 struct irqaction secondary 成员来记录一个新的线程,用来运行传入的 thread_fn。
- 普通情况下,函数对用户传入的 handler 和 thread_fn 进行了修改:用户传入的 handler 变成了线程的 thread_fn,而对应的 handler 赋值为系统默认的 irq_default_primary_handler,根据中断处理流程的描述, action->handler 会被执行,该函数返回 IRQ_WAKE_THREAD 时就会唤醒线程,这时候用户传入的 handler 就放到内核中执行了;
- 强制中断线程化这个配置选项感觉有点一刀切的意思了,驱动开发者在编写驱动程序的时候,通常会假设中断处理程序运行在中断上下文中,以这个前提来做一些并发或者优化的处理,把这些处理函数放到线程中处理倒也不是不行,只是感觉有些怪,暂时没见过使用强制线程化配置的系统;
- 在默认不支持强制线程化的系统中,用户要创建中断线程就通过 request_threaded_irq 函数注册中断,传入一个thread_fn 函数作为回调函数,但是这个函数并不是该线程的执行函数,和我们使用 kthread_create 传入的 thread_fn 不是一个概念,只是作为一个在线程中会被调用到的函数;
3.3 中断线程化
/* * 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) { 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; } }
3.3.1 setup_irq_thread
其中创建中断线程对应的源码为:
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, }; 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); /* * 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。 */ 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; }
3.3.2 irq_thread
对应的线程函数为 irq_thread:
/* * Interrupt handler thread */ static int irq_thread(void *data) { struct callback_head on_exit_work; struct irqaction *action = data; // 获取线程参数irqaction struct irq_desc *desc = irq_to_desc(action->irq); // 根据IRQ编号获取中断描述符 irqreturn_t (*handler_fn)(struct irq_desc *desc, // 函数指针 struct irqaction *action); if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD, // 如果是强制线程化 &action->thread_flags)) handler_fn = irq_forced_thread_fn; else handler_fn = irq_thread_fn; init_task_work(&on_exit_work, irq_thread_dtor); task_work_add(current, &on_exit_work, false); irq_thread_check_affinity(desc, action); while (!irq_wait_for_interrupt(action)) { // 1 irq_thread_check_affinity(desc, action); action_ret = handler_fn(desc, action); //2 执行action->thread_fn() if (action_ret == IRQ_WAKE_THREAD) irq_wake_secondary(desc, action); //3 wake_threads_waitq(desc); // 唤醒所有等待当前中断执行完成的线程 } /* * 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(current, irq_thread_dtor); return 0; }
注1:irq_thread 的 data 参数为 action,在申请时 action 可能有两种:
- 一个是正常的由用户传递参数生成的 action;
- 另一个是强制线程化生成的 action;
根据 action 类型的不同,handle_fn 被赋值为不同的回调函数irq_forced_thread_fn、irq_thread_fn,这部分属于内核线程的初始化部分。
接下来就是一个 while 循环,判断 irq_wait_for_interrupt 的返回值,这个函数中也是循环地判断 action->thread_flags 中的 IRQTF_RUNTHREAD 标志位是否被置位,如果该标志没有被置位,线程将陷入 TASK_INTERRUPTIBLE 类型的睡眠,如果置位,则会设置任务状态为TASK_RUNNING,并返回0。
那么,谁来将这个标志位置位并唤醒当前线程呢?
还是要把目光放回到中断处理流程中,回顾一 lowlevel 的中断处理函数:
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags) { for_each_action_of_desc(desc, action) { irqreturn_t res; res = action->handler(irq, action->dev_id); // 执行primary handler switch (res) { case IRQ_WAKE_THREAD: __irq_wake_thread(desc, action); // 唤醒中断线程 ... } ... } }
也就是说,lowlevel 中断处理部分,不论是普通中断处理还是线程化的中断处理,action->handler 还是会被执行,只不过对于线程化中断,中断处理的返回值为 IRQ_WAKE_THREAD,这时候将调用 __irq_wake_thread 设置 IRQTF_RUNTHREAD 标志位,并执行 wake_up_process(action->thread) 唤醒中断线程。
注2: 中断线程被唤醒之后,将会调用初始化阶段赋值的 handler_fn,如果是强制线程化的中断,对应 irq_forced_thread_fn 否则对应 irq_thread_fn。
/* * Interrupts which are not explicitly requested as threaded * interrupts rely on the implicit bh/preempt disable of the hard irq * context. So we need to disable bh here to avoid deadlocks and other * side effects. */ static irqreturn_t irq_forced_thread_fn(struct irq_desc *desc, struct irqaction *action) { irqreturn_t ret; local_bh_disable(); // 关中断下半部 ret = action->thread_fn(action->irq, action->dev_id); // 执行threaded interrupt handler if (ret == IRQ_HANDLED) atomic_inc(&desc->threads_handled); irq_finalize_oneshot(desc, action); local_bh_enable(); // 开中断下半部 return ret; } /* * Interrupts explicitly requested as threaded interrupts want to be * preemtible - 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; ret = action->thread_fn(action->irq, action->dev_id); // 执行threaded interrupt handler if (ret == IRQ_HANDLED) atomic_inc(&desc->threads_handled); irq_finalize_oneshot(desc, action); return ret; }
这两个函数的处理其实差不大多,都是调用 action->thread_fn 函数,也就是用户传入的回调函数,不同的是,如果是强制中断线程化的处理,需要关中断下半部,下半部主要是 softirq(tasklet 基于 softirq),为啥呢?
- 这是因为用户调用 request_irq 接口注册一个中断,但是内核强制中断线程化将这个中断处理函数放到了线程中,这就有可能导致:在用户看来,我的中断处理函数就是在中断上下文中执行的,因此优先级肯定比下半部高,在实际编码过程中也是依据了这一准则,但实际上放到线程中执行之后,优先级就比中断下半部低了,这就会导致死锁或者其它的问题。
注3:如果上述的 handler_fn 依旧返回 IRQ_WAKE_THREAD,那么就会唤醒 secondary action,也就是在支持强制中断线程化的系统中,用户在注册中断时既传入了 handler 又传入了 thread_fn,而且 handler 的返回值为 IRQ_WAKE_THREAD,这种情况下,用户就可以把一部分工作放到中断环境下运行,另一部分工作放到内核线程环境中执行。
在所有工作处理完成之后,调用 wake_threads_waitq 唤醒所有等待当前中断执行完成的进程。
3.4 共享中断处理
如果一个IRQ编号被若干个设备共享,那么一个IRQ编号对应着若干个irqaction,在编写设备驱动时,进行设备中断注册和释放的时候我们需要通过dev_id区分具体是哪一个irqaction。
同样当中断发生的会后,linux中断子系统会去遍历IRQ编号上注册的irqaction的handler回调函数,这样,虽然只是一个外设产生的中断,linux kernel还是把所有共享的那些中断handler都逐个调用执行。为了让系统的performance不受影响,irqaction的handler函数必须在函数的最开始进行判断,是否是自己的硬件设备产生了中断(读取硬件的寄存器),如果不是,尽快的退出。
代码通过判断desc->action来识别这是不是一个共享中断。如果desc->action不为空,说名这个中断已经被其他设备申请过,也就是这是一个共享中断。
接下来会判断这个新申请的中断与已经申请的旧中断的以下几个标志是否一致:
- 一定要设置了IRQF_SHARED标志(共享中断必须设置该函数);
- 电气触发方式要完全一样(IRQF_TRIGGER_XXXX);
- IRQF_PERCPU要一致;
- IRQF_ONESHOT要一致;
代码如下:
/* * 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 (desc->istate & IRQS_NMI) { 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) || // 校验IRQF_SHARED、IRQF_ONESHOP是否一致 (oldtype != (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) != // 校验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; }
检查这些条件都是因为多个设备试图共享一根中断线,试想一下,如果一个设备要求上升沿中断,一个设备要求电平中断,然而只有一根中断线,实际上只可能设置一种触发类型。因此共享中断必须具有相同电气触发类型(电平、边沿、极性)、以及共同标志 IRQF_SHARED、IRQF_ONESHOT、IRQF_PERCPU。
完成检查后,函数找出action链表中最后一个irqaction实例的指针。并设置共享中断标志位shared=1。
3.5 非共享中断处理
如果这不是一个共享中断,或者是共享中断的第一次申请,将进行如下操作:
- 调用init_waitqueue_head初始化irq_desc结构中断线程等待队列:wait_for_threads;用于中断的同步,比如内核中执行 irq 的 disable 或者 free 时,需要等待最后一次中断的完成,等待的进程就会睡眠在该等待队列上;
- 设置触发模式,用户在注册时可以通过传入 IRQF_TRIGGER_LOW/IRQF_TRIGGER_HIGH 等标志位设置中断源的触发方式,如果用户没有设置,就使用默认的触发方式,这个默认方式为低电平触发。
- 中断的使能,对于 arm 而言,中断使能就是设置中断控制器的相关寄存器,通过调用 irq_chip 的回调函数进行设置;
- 设置中断的 CPU 亲和性,一般来说,中断会被默认设置分发到一个 CPU 上,而不会分发多个 CPU,毕竟如果分发到多个 CPU,一个中断源触发多个 CPU 进入到中断处理模式,但是最后只有一个 CPU 真正执行处理,这是一种浪费。同时,用户还可以设置中断是否可迁移,断的 target CPU 可以通过用户空间的 /proc 目录下的文件进行修改,用户空间也存在 irq balancing 的 demon 进程,该进程可以根据 CPU 负载动态调整中断的 CPU 亲和性,基于某些实际应用的考虑,用户可以设置禁止中断在 CPU 之间的迁移;
代码如下:
if (!shared) { // 非共享中断 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_unlock; } /* * Activate the interrupt。 That activation must happen * independently of IRQ_NOAUTOEN。 request_irq() can fail * and the callers are supposed to handle * that。 enable_irq() of an interrupt requested with * IRQ_NOAUTOEN is not supposed to fail。 The activation * keeps it in shutdown mode, it merily associates * resources if necessary and if that's not possible it * fails。 Interrupts which are in managed shutdown mode * will simply ignore that activation request。 */ ret = irq_activate(desc); if (ret) goto out_unlock; desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \ IRQS_ONESHOT | IRQS_WAITING); irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); // 清除IRQD_IRQ_INPROGRESS if (new->flags & IRQF_PERCPU) { //如果设置了IRQF_PERCPU irqd_set(&desc->irq_data, IRQD_PER_CPU); irq_settings_set_per_cpu(desc); } if (new->flags & IRQF_ONESHOT) //如果设置了IRQS_ONESHOT desc->istate |= IRQS_ONESHOT; /* 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); } if (irq_settings_can_autoenable(desc)) { irq_startup(desc, IRQ_RESEND, IRQ_START_COND); } else { /* * 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。 */ WARN_ON_ONCE(new->flags & IRQF_SHARED); /* Undo nested disables: */ desc->depth = 1; } } else if (new->flags & IRQF_TRIGGER_MASK) { // 如果设置了电气触发类型 unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK; unsigned int omsk = irqd_get_trigger_type(&desc->irq_data); if (nmsk != omsk) /* hope the handler works with current trigger mode */ pr_warn("irq %d uses trigger mode %u; requested %u\n", irq, omsk, nmsk); }
3.6 设置irqaction
如果是非共享中断,则设置desc->action=new;否则把新的irqaction实例链接到action链表的最后:
*old_ptr = new; 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); chip_bus_sync_unlock(desc); mutex_unlock(&desc->request_mutex); irq_setup_timings(desc, new);
然后,初始化该中断相关的统计参数,如 irq_count/irqs_unhandled。
3.7 注册/proc文件节点
最后,调用wake_up_process唤醒中断线程,然后调用register_irq_proc注册相关的/proc文件节点:
/* * Strictly no need to wake it up, but hung_task complains * when no hard interrupt wakes the thread up。 */ if (new->thread) wake_up_process(new->thread); if (new->secondary) wake_up_process(new->secondary->thread); register_irq_proc(irq, desc); new->dir = NULL; register_handler_proc(irq, new);
至此,irq的申请宣告完毕。
参考文章
[1]Linux中断(interrupt)子系统之三:中断流控处理层