运行环境
kernel 5.10
CPU Ti am33xx
linux中断的3个结构体
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; struct irqaction *action; /* IRQ action list */ // 里面存放了用户注册的中断服务代码 unsigned int status_use_accessors; unsigned int core_internal_state__do_not_mess_with_it; unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int tot_count; unsigned int irq_count; /* For detecting broken IRQs */ unsigned long last_unhandled; /* Aging timer for unhandled count */ unsigned int irqs_unhandled; atomic_t threads_handled; int threads_handled_last; raw_spinlock_t lock; struct cpumask *percpu_enabled; const struct cpumask *percpu_affinity; #ifdef CONFIG_SMP const struct cpumask *affinity_hint; struct irq_affinity_notify *affinity_notify; #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; #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;
struct irqaction { irq_handler_t handler; // 用户注册的中断服务代码, 中断发生时就会运行这个中断处理函数 void *dev_id; void __percpu *percpu_dev_id; struct irqaction *next; irq_handler_t thread_fn; struct task_struct *thread; struct irqaction *secondary; unsigned int irq; //hw中断号, unsigned int flags; //中断标志,注册时设置,比如上升沿中断,下降沿中断等 unsigned long thread_flags; unsigned long thread_mask; const char *name; //中断名称,产生中断的硬件的名字 struct proc_dir_entry *dir; // proc/irq/ } ____cacheline_internodealigned_in_smp;
struct irq_data { u32 mask; unsigned int irq; unsigned long hwirq; struct irq_common_data *common; struct irq_chip *chip; // 芯片端的中断处理函数 struct irq_domain *domain; // 代表一个interrupt controller 设备树中的INTC #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY struct irq_data *parent_data; #endif void *chip_data; };
整体框图如下:
linux硬件中断处理流程
1. 外设产生中断
2. CPU触发中断
3. 跳转到异常向量入口:
保存现场
执行中断服务
恢复现场
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
我们主要关注如何执行中断服务,在arch\arm\kernel\entry-armv.S中,有如下几条语句
.macro irq_handler #ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER ldr r1, =handle_arch_irq mov r0, sp badr lr, 9997f ldr pc, [r1] #else arch_irq_handler_default #endif
其中 handle_arch_irq 即为CPU处理中断时会调用到的硬件中断服务入口,handle_arch_irq在 kernel/irq/handle.c 中被赋值
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER #ifndef CONFIG_IRQCHIP_XILINX_INTC_MODULE_SUPPORT_EXPERIMENTAL int __init set_handle_irq(void (*handle_irq)(struct pt_regs *)) #else int set_handle_irq(void (*handle_irq)(struct pt_regs *)) #endif { if (handle_arch_irq) return -EBUSY; handle_arch_irq = handle_irq; return 0; } #endif
set_handle_irq 被谁调用?主要由CPU厂家的BSP工程师调用,比如TI的AM33xx系列平台,在 drivers/irqchip/irq-omap-intc.c 中的以下接口调用
static int __init intc_of_init(struct device_node *node, struct device_node *parent) { int ret; omap_nr_pending = 3; omap_nr_irqs = 96; if (WARN_ON(!node)) return -ENODEV; if (of_device_is_compatible(node, "ti,dm814-intc") || of_device_is_compatible(node, "ti,dm816-intc") || of_device_is_compatible(node, "ti,am33xx-intc")) { omap_nr_irqs = 128; omap_nr_pending = 4; } ret = omap_init_irq(-1, of_node_get(node)); if (ret < 0) return ret; set_handle_irq(omap_intc_handle_irq); // 设置硬中断服务函数入口 return 0; }
omap_intc_handle_irq便是am33xx触发中断后,需要响应的中断服务入口,该入口做了哪些工作?
static asmlinkage void __exception_irq_entry omap_intc_handle_irq(struct pt_regs *regs) { extern unsigned long irq_err_count; u32 irqnr; irqnr = intc_readl(INTC_SIR); // 获取硬件中断号 /* * A spurious IRQ can result if interrupt that triggered the * sorting is no longer active during the sorting (10 INTC * functional clock cycles after interrupt assertion). Or a * change in interrupt mask affected the result during sorting * time. There is no special handling required except ignoring * the SIR register value just read and retrying. * See section 6.2.5 of AM335x TRM Literature Number: SPRUH73K * * Many a times, a spurious interrupt situation has been fixed * by adding a flush for the posted write acking the IRQ in * the device driver. Typically, this is going be the device * driver whose interrupt was handled just before the spurious * IRQ occurred. Pay attention to those device drivers if you * run into hitting the spurious IRQ condition below. */ if (unlikely((irqnr & SPURIOUSIRQ_MASK) == SPURIOUSIRQ_MASK)) { pr_err_once("%s: spurious irq!\n", __func__); irq_err_count++; omap_ack_irq(NULL); return; } irqnr &= ACTIVEIRQ_MASK; handle_domain_irq(domain, irqnr, regs); // 根据硬件中断号,在domain链表中查找映射好的IRQ number, 实现原理是怎样的? }
domain是如何实现HW 中断号和 IRQ number映射的? 在解析设备树的时候,会调用 irq_of_parse_and_map 接口创建映射;详情参考 linux中断号映射
handle_domain_irq 调用了 __handle_domain_irq, 在__handle_domain_irq中,开始调用硬中断服务函数,也就是中断上半部
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq, bool lookup, struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); unsigned int irq = hwirq; int ret = 0; irq_enter(); #ifdef CONFIG_IRQ_DOMAIN if (lookup) irq = irq_find_mapping(domain, hwirq); // 根据硬中断号,在映射表中找到对应的 IRQ number #endif /* * Some hardware gives randomly wrong interrupts. Rather * than crashing, do something sensible. */ if (unlikely(!irq || irq >= nr_irqs)) { ack_bad_irq(irq); ret = -EINVAL; } else { generic_handle_irq(irq); // 根据IRQ number,查找中断服务,并执行 } irq_exit(); // 退出硬件中断服务,然后 invoke_softirq(),再 __do_softirq(),唤醒softirq队列,进入下半部开始执行 set_irq_regs(old_regs); return ret; }
他会调用 generic_handle_irq(irq) , 以下为 generic_handle_irq 代码
int generic_handle_irq(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); // 根据IRQ number 找到 中断描述符 struct irq_data *data; if (!desc) return -EINVAL; data = irq_desc_get_irq_data(desc); if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data))) return -EPERM; generic_handle_irq_desc(desc); // 根据中断描述符,找到对应的 handler return 0; }
generic_handle_irq_desc 会调用 desc结构体中的 hande_irq 成员: desc->handle_irq(desc); 他指向哪 ?
desc->handle_irq(desc) 最后会调用到 irqaction 结构体中的 handler 成员,也就是用户通过 request_irq (request_irq详解) 接口注册的中断服务程序,附上网上找的一份简单示意图(kernel2.6)
desc->handle_irq(desc) 是如何关联到 irqaction 结构体中的 handler 成员?
先从设备端看:
拿AM33xx GPIO举例:
在 gpio-omap.c 中的 omap_gpio_probe 里面,会对 irq_chip 结构体的 irq_set_type 赋值 omap_gpio_irq_type,然后将 am33xx芯片端的gpio 中断处理相关的函数注册到linux kernel,kernel调用 desc->handle_irq(desc) 时,即可调用到 am33xx xinp端的 gpio 中断处理函数(具体细节接下来分析);
omap_gpio_irq_type 函数如下:
static int omap_gpio_irq_type(struct irq_data *d, unsigned type) { struct gpio_bank *bank = omap_irq_data_get_bank(d); int retval; unsigned long flags; unsigned offset = d->hwirq; if (type & ~IRQ_TYPE_SENSE_MASK) return -EINVAL; if (!bank->regs->leveldetect0 && (type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH))) return -EINVAL; raw_spin_lock_irqsave(&bank->lock, flags); retval = omap_set_gpio_triggering(bank, offset, type); if (retval) { raw_spin_unlock_irqrestore(&bank->lock, flags); goto error; } omap_gpio_init_irq(bank, offset); if (!omap_gpio_is_input(bank, offset)) { raw_spin_unlock_irqrestore(&bank->lock, flags); retval = -EINVAL; goto error; } raw_spin_unlock_irqrestore(&bank->lock, flags); if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) irq_set_handler_locked(d, handle_level_irq); // 电平触发中断 else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) /* * Edge IRQs are already cleared/acked in irq_handler and * not need to be masked, as result handle_edge_irq() * logic is excessed here and may cause lose of interrupts. * So just use handle_simple_irq. */ irq_set_handler_locked(d, handle_simple_irq); // 边沿触发中断 return 0; error: return retval; }
该函数主要是设置了 gpio 电平触发, 边沿触发的中断处理服务,先拿边沿触发分析,看看 handle_simple_irq 函数是如何一步步调用到 irqaction 结构体中的 handler 成员的(也就是用户通过request_irq 申请的中断服务):
void handle_simple_irq(struct irq_desc *desc) { raw_spin_lock(&desc->lock); if (!irq_may_run(desc)) goto out_unlock; desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) { desc->istate |= IRQS_PENDING; goto out_unlock; } kstat_incr_irqs_this_cpu(desc); // 设置处理标记,避免多次重复进入中断处理 handle_irq_event(desc); // 中断服务相关 out_unlock: raw_spin_unlock(&desc->lock); }
再来看看 handle_irq_event(desc) 做了啥 irqreturn_t handle_irq_event(struct irq_desc *desc)
{ irqreturn_t ret; desc->istate &= ~IRQS_PENDING; irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); raw_spin_unlock(&desc->lock); ret = handle_irq_event_percpu(desc); // 进入中断服务 raw_spin_lock(&desc->lock); irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); return ret; } irqreturn_t handle_irq_event_percpu(struct irq_desc *desc) { irqreturn_t retval; unsigned int flags = 0; retval = __handle_irq_event_percpu(desc, &flags); // 进入中断服务 add_interrupt_randomness(desc->irq_data.irq); if (!noirqdebug) note_interrupt(desc, retval); return retval; }
接下来就真正调用到了 irqaction 结构体中的 handler 成员
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags) { 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; /* * 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); res = action->handler(irq, action->dev_id); // 调用用户注册的中断服务 trace_irq_handler_exit(irq, action, 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: /* * 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); fallthrough; /* to add to randomness */ case IRQ_HANDLED: *flags |= action->flags; break; default: break; } retval |= res; } return retval; }
omap_gpio_probe 是如何将 irq_chip 关联到 irq_desc 结构体的, 也就是说 kernel调用 desc->handle_irq(desc) 时,怎么就能调用到 handle_simple_irq?后续继续分析!
标签:handle,struct,int,irq,流程,am33xx,中断,linux,desc From: https://www.cnblogs.com/weishengzhong/p/18020953