am33xx linux中断处理流程

运行环境

  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?后续继续分析!

  

posted @ 2024-02-20 15:33  迷途小菜鸟  阅读(10)  评论(0编辑  收藏  举报