中断系统中的设备树(一)__Linux对中断处理的框架分析

    .section .vectors, "ax", %progbits
.L__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)    pc, .L__vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq      @当发生中断时执行该指令
    W(b)    vector_fiq

这些地址就是vector,可以放在0地址,也可以放在0xffff0000(对于这个地址是启动mmu之后才存在的)对于其它芯片,向量所在地址可能不同,但都是用来处理异常打开内核源码 a. 异常向量入口: arch\arm\kernel\entry-armv.S

执行上面的程序之后,最后会执行下面的汇编程序:

/*
 * Interrupt dispatcher
 */
	vector_stub	irq, IRQ_MODE, 4

	.long	__irq_usr			@  0  (USR_26 / USR_32)
	.long	__irq_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__irq_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__irq_svc			@  3  (SVC_26 / SVC_32)
	.long	__irq_invalid			@  4
	.long	__irq_invalid			@  5
	.long	__irq_invalid			@  6
	.long	__irq_invalid			@  7
	.long	__irq_invalid			@  8
	.long	__irq_invalid			@  9
	.long	__irq_invalid			@  a
	.long	__irq_invalid			@  b
	.long	__irq_invalid			@  c
	.long	__irq_invalid			@  d
	.long	__irq_invalid			@  e
	.long	__irq_invalid			@  f
__irq_usr:
	usr_entry
	kuser_cmpxchg_check
	irq_handler
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq

跟踪中断irq_handler,在该文件中查找,可以查到如下程序:

/*
 * Interrupt handling.
 */
	.macro	irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
	ldr	r1, =handle_arch_irq --------------------->1
	mov	r0, sp
	badr	lr, 9997f
	ldr	pc, [r1]

会调用该函数,该函数应该会读取寄存器,是哪个中断发生了,该函数会被下面的程序调用,

int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
	if (handle_arch_irq)
		return -EBUSY;

	handle_arch_irq = handle_irq; //---------------1.1
	return 0;
}

在linux内核中对于每一种芯片都会调用set_handle_irq函数设置自己的芯片的对应的函数,譬如:

set_handle_irq(s3c24xx_handle_irq);

对于2440芯片用s3c24xx_handle_irq函数处理中断,s3c24xx_handle_irq函数也就是处理中断的C函数入口,该函数的定义如下:

asmlinkage void __exception_irq_entry s3c24xx_handle_irq(struct pt_regs *regs)
{
	do {
		if (likely(s3c_intc[0]))
			if (s3c24xx_handle_intc(s3c_intc[0], regs, 0))
				continue;

		if (s3c_intc[2])
			if (s3c24xx_handle_intc(s3c_intc[2], regs, 64))
				continue;

		break;
	} while (1);
}

中断调用过程框架分析:

中断控制器可以看出有32bit,每一位代表一种中断,也就是说这个中断控制器可以向CPU发出32种中断,每一种中断的处理函数
都不一样,那么在linux内核中怎样管理这些中断和中断处理函数呢?

1、最简单方法是创建指针数组,每一项对应一个中断,在这个中断里存放处理函数,这个数组项用irq_desc中断结构体来表示

irq_desc中断结构体中的handle_irq是中断的总的处理函数,action指向一个或者多个struct irqaction,对于每一号中断,在irq_desc数组中都会有对应的项,在每一项都有会有action指向struct irqaction,在struct irqaction中存放具体的处理函数handler。

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;
	unsigned int		flags;
	unsigned long		thread_flags;
	unsigned long		thread_mask;
	const char		*name;
	struct proc_dir_entry	*dir;
} ____cacheline_internodealigned_in_smp;

在irq_desc中有一个handle_irq,在action指向struct irqaction中有handler,他们都是函数指针,他们有什么联系呢?

handle_irq:

  •  调用action链表中的handler(调用我们提供的具体的函数);          
  • 清中断(是芯片相关的操作) irq_data.chip含有中断相关的处理(使能中断、屏蔽中断、清中断...)
  • irq_desc结构体中有一个irq_data结构体,
    struct irq_data { 
        u32            mask;----------TODO 
        unsigned int        irq;--------IRQ number 
        unsigned long        hwirq;-------HW interrupt ID 
        unsigned int        node;-------NUMA node index 
        unsigned int        state_use_accessors;--------底层状态,参考IRQD_xxxx 
        struct irq_chip        *chip;----------该中断描述符对应的irq chip数据结构 
        struct irq_domain    *domain;--------该中断描述符对应的irq domain数据结构 
        void            *handler_data;--------和外设specific handler相关的私有数据 
        void            *chip_data;---------和中断控制器相关的私有数据 
        struct msi_desc        *msi_desc; 
        cpumask_var_t        affinity;-------和irq affinity相关 
    };
    struct irq_chip {
    	struct device	*parent_device;
    	const char	*name;
    	unsigned int	(*irq_startup)(struct irq_data *data);
    	void		(*irq_shutdown)(struct irq_data *data);
    	void		(*irq_enable)(struct irq_data *data);
    	void		(*irq_disable)(struct irq_data *data);
      ......      
    };

    发生中断时内核的执行过程:
    cpu跳到vector_irq, 保存现场, 调用C函数handle_arch_irq

    handle_arch_irq:
    a. 读 int controller, 得到hwirq
    b. 根据hwirq得到virq
    c. 调用 irq_desc[virq].handle_irq

    如果该中断没有子中断, irq_desc[virq].handle_irq的操作:
    a. 取出irq_desc[virq].action链表中的每一个handler, 执行它
    b. 使用irq_desc[virq].irq_data.chip的函数清中断

通过handle_irq来调用我们提供的处理函数、清除中断,对于我们写驱动程序的人,只需要写具体的中断处理函数就可以了,在具体的中断处理函数中我们只需要关系我们需要实现的功能,不需要关系芯片相关的中断使能、中断清除等工作。

我们现在加上一个芯片,加上一个或芯片,当网卡或者摄像头有数据时会产生中断,(网卡和摄像头公用一个中断引脚),但是它们有各自的中断处理函数(譬如:网卡的中断处理函数是irq_net,摄像头的中断处理函数是irq_cam),其实这并不复杂,irq_desc结构体中的action链表,由于在0号中断上面有两个设备,action指向的链表应该至少含有两个struct irqaction结构体节点,如下图所示:

像这样公用一个中断也就是共享中断,对于共享中断来说action链表中的每一个handler函数都会被执行一次,具体是哪个中断
必须交给硬件的中断函数来区分。

 

如上图所示,当发生外部中断INT4、INT5、INT6、INT7之一时,子中断控制器(sub  interrupt controller )都会向上一级的中断控制器(interrupt controller)发出信号,上一级的中断控制器(interrupt controller)向CPU发出中断,然后CPU读取寄存器(譬如:INPUD)时就会发现是4号中断,然后读取SUBSRCPND寄存器具体是哪个子中断,那么我们irq_data结构体中的handle_irq指向s3c_irq_demux(中断分发函数),如下图所示:

  • hwirq(表示硬件中断号)
  • (virq(表示虚拟中断号)

s3c_irq_demux做了以下几件事
如果该中断是由子中断产生, irq_desc[virq].handle_irq的操作:
a. 读 sub int controller, 得到hwirq'
b. 根据hwirq'得到virq
c. 调用 irq_desc[virq].handle_irq

硬件中断号和虚拟中断号

我们可以通过硬件中断号,得到虚拟中断号,这些虚拟中断号就是irq_desc[]数组项的下标 可以定义这么一个公式

virq = hwirq  + offset + 1
     = hwirq + 16
virq = hwirq' + offset + 2
     = hwirq' + 58 + 16

根据虚拟中断号virq ==> 得到irq_desc.handle_irq ==> 调用我们注册的具体的中断处理函。

我们在EINT5接上一个按键,按键的中断处理函数是irq_key,我们注册这个中断时会注册对应的中断号(这里是37)根据中断号就会找到对应的irq_desc结构体,然后就会创建一个irqaction结构体,让irqaction.handler指向irq_key(我们提供的具体的函数),如下图所示:

当按键被按下后,子中断控制器(sub  interrupt controller )都会向上一级的中断控制器(interrupt controller)发出信号,上一级的中断控制器(interrupt controller)向CPU发出中断,然后CPU读取寄存器(INPUD)时就会发现是4号中断,然后执行irq_desc.handle_irq ==>s3c_irq_demux函数==>调用irq_desc[virtirq].handle_irq(遍历执行action链表) ==》irq_key。

源程序分析:

在前面说过对于S3C2440, s3c24xx_handle_irq 是用于处理中断的C语言入口函数。

asmlinkage void __exception_irq_entry s3c24xx_handle_irq(struct pt_regs *regs)
{
	do {
		if (likely(s3c_intc[0]))
			if (s3c24xx_handle_intc(s3c_intc[0], regs, 0))  //------------------1
				continue;

		if (s3c_intc[2])
			if (s3c24xx_handle_intc(s3c_intc[2], regs, 64))
				continue;

		break;
	} while (1);
}

1、

static inline int s3c24xx_handle_intc(struct s3c_irq_intc *intc,
				      struct pt_regs *regs, int intc_offset)
{
	int pnd;
	int offset;

	pnd = readl_relaxed(intc->reg_intpnd);  // 读intpnd寄存器判断是哪个中断等待处理
	if (!pnd)
		return false;

	/* non-dt machines use individual domains */
	if (!irq_domain_get_of_node(intc->domain))
		intc_offset = 0;

	/* We have a problem that the INTOFFSET register does not always
	 * show one interrupt. Occasionally we get two interrupts through
	 * the prioritiser, and this causes the INTOFFSET register to show
	 * what looks like the logical-or of the two interrupt numbers.
	 *
	 * Thanks to Klaus, Shannon, et al for helping to debug this problem
	 */
	offset = readl_relaxed(intc->reg_intpnd + 4);

	/* Find the bit manually, when the offset is wrong.
	 * The pending register only ever contains the one bit of the next
	 * interrupt to handle.
	 */
	if (!(pnd & (1 << offset)))
		offset =  __ffs(pnd); //获得中断号

	handle_domain_irq(intc->domain, intc_offset + offset, regs);   // --------------1.1
	return true;
}

1.1、

/*
 * Convert a HW interrupt number to a logical one using a IRQ domain,
 * and handle the result interrupt number. Return -EINVAL if
 * conversion failed. Providing a NULL domain indicates that the
 * conversion has already been done.
 */
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
			bool lookup, struct pt_regs *regs);

static inline int handle_domain_irq(struct irq_domain *domain,
				    unsigned int hwirq, struct pt_regs *regs)
{
	return __handle_domain_irq(domain, hwirq, true, regs);  // ----------------1.1.1
}

1.1.1、

/**
 * __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
 * @domain:	The domain where to perform the lookup
 * @hwirq:	The HW irq number to convert to a logical one
 * @lookup:	Whether to perform the domain lookup or not
 * @regs:	Register file coming from the low-level handling code
 *
 * Returns:	0 on success, or -EINVAL if conversion has failed
 */
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);  //根据硬件中断号计算出虚拟中断号
#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);  // ---------1.1.1.1
	}

	irq_exit();
	set_irq_regs(old_regs);
	return ret;
}

1.1.1.1、

/**
 * generic_handle_irq - Invoke the handler for a particular irq
 * @irq:	The irq number to handle
 *
 */
int generic_handle_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq);  //根据虚拟终端号获得irq_desc结构体

	if (!desc)
		return -EINVAL;
	generic_handle_irq_desc(desc);  //调用desc->handle_irq()
	return 0;
}

 

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值