5.2中断系统中的设备树——Linux对中断处理的框架及代码流程简述

当发生中断时,CPU会跳到一个固定的地址去执行代码,这个固定的地址就被称为中断向量

ARM920T为例,它的中断向量默认是地址24(0x18)的地方。那么,就可以在这里放一条跳转指令

一系列的跳转指令用来处理各种异常,中断也是一种异常

这些指令就被称为vector异常向量表

对于ARM9来说,vector可以放在0地址,也可以放在0xffff 0000地址(使能MMU,开启虚拟地址之后)。

对于其他芯片,vector的存放地址可能不一样。但是不管怎么样,对于大部分的芯片来说,它的软件中都应该保存一个vector,用来处理各种异常。

在linux内核中,也有一个vector。

打开arch\arm\kernel\entry-armv.S,可以找到一个vectors,里面存放的就是异常向量。

当发生中断时,应该执行这条指令,跳到 vector_irq 标号所示的段去执行代码。

W(b)    vector_irq

搜索代码,是找不到 vector_irq 标号的,它是通过vector_stub宏来设置的。

宏的设置如下。

结合宏的定义,可以看出就是设置一个标号为vector_irq的段,省略号省略的是宏中描述的其他代码,这些代码暂不研究,它们应该会调用后面的相关函数

比如,当中断发生在用户态时,调用“.long    __irq_usr”;当中断发生在管理模式下时,调用“.long    __irq_svc”。

不管是用户态还是管理模式,最终,它们都会调用同一个函数

看一下它们是如何调用到同一个函数的。

首先是__irq_usr,在同一个文件中可以找到__irq_usr的标号。

根据中断的一般处理流程:

  1. 保存现场;
  2. 处理中断;
  3. 恢复现场;

可以猜测,usr_entry是保存现场,irq_handler是处理中断,ret_to_user_from_irq是恢复现场。

再来看一下__irq_svc

类似的,svc_entry应该是保存现场,irq_handler是处理中断,svc_exit应该是恢复现场和退出中断。

那么,也就是说__irq_usr__irq_svc,都是通过irq_handler来处理中断。

查看一下irq_handler的定义,它的定义也是在同个文件下。

可以看到,irq_handler是一个handle_arch_irq是一个函数指针,最终会调用到handle_arch_irq所指向的函数

猜测在这个函数中,应该会读取中断控制器判断是哪个中断发生了然后调用对应的中断处理函数处理中断

既然handle_arch_irq是一个函数指针,那么是在哪里设置的呢?

搜索handle_arch_irq,可以在 kernel\irq\handle.c 文件中发现对应的设置函数set_handle_irq。

再搜索一下set_handle_irq,看看是哪里调用这个函数。

从搜索结果可以知道,对于每款芯片,它们各自都会调用set_handle_irq函数,来设置它们自己的中断处理的入口函数。

对应s3c2440芯片,则使用s3c24xx_handle_irq函数,作为自己的中断处理的入口函数。

s3c24xx_handle_irq函数如下。

可以从这个函数开始分析,内核对中断的处理框架

在分析代码之前,先说明一下。

假设这是CPU中断控制器

可以看到,这个中断控制器有32位,每一位代表一种中断,也就是说这个中断控制器可以向CPU发出32种中断,并且每一种中断的处理函数都不一样。

问:在Linux内核里面,怎么管理这么多种中断和它们的处理函数呢?

答:最简单的方法就是,创建一个指针数组,0号元素对应0号中断,1号元素对应1号中断。在数组中存放对应中断的处理函数。

事实上,内核上的处理方法也是类似的,只不过数组项用一个结构体irq_desc(中断描述结构体)来表示。

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;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
	irq_preflow_handler_t	preflow_handler;
#endif
	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		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;

在内核中,有一个irq_desc数组,里面放的是irq_desc结构体。

 主要关注irq_desc结构体的三个成员:

  1. handle_irq,总的中断处理函数;
  2. action,是一个链表,指向一个或多个irqaction结构体。
    irqaction结构体里面,有一个handler成员,它是一个函数指针,指向我们提供的具体的中断处理函数
  3. irq_data;

问:在irq_desc结构体中有一个函数指针handle_irq,在irqaction结构体中又有一个函数指针handler,它们都是处理中断的,为什么会有两个处理中断的函数呢?

答:显然,他们的作用不会完全相同。

首先看一下handle_irq做什么事情:

  1. 调用action链表中的handler
  2. 清中断;

这样做有一个好处,就是我们只需要提供具体的中断处理函数,专注于我们自己要做的事情就可以了,而不需要我们自己去清中断

那么,handle_irq要怎么清中断呢?

清中断是芯片相关的操作,这就要用到irq_data了。

struct irq_data {
	u32			mask;
	unsigned int		irq;
	unsigned long		hwirq;
	struct irq_common_data	*common;
	struct irq_chip		*chip;
	struct irq_domain	*domain;
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_data		*parent_data;
#endif
	void			*chip_data;
};

irq_data结构体中含有一个chip成员,它是一个指向irq_chip结构体的指针。

irq_chip结构体里面含有各种函数指针,每一个函数指针指向的函数都代表一个功能,比如清中断(irq_ack),关闭中断(irq_disable),使能中断(irq_enable)等等。

irq_chip结构体在include\linux\irq.h中定义,内有各个函数指针的详细说明。)

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);

	void		(*irq_ack)(struct irq_data *data);
	void		(*irq_mask)(struct irq_data *data);
	void		(*irq_mask_ack)(struct irq_data *data);
	void		(*irq_unmask)(struct irq_data *data);
	void		(*irq_eoi)(struct irq_data *data);

	int		(*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
	int		(*irq_retrigger)(struct irq_data *data);
	int		(*irq_set_type)(struct irq_data *data, unsigned int flow_type);
	int		(*irq_set_wake)(struct irq_data *data, unsigned int on);

	void		(*irq_bus_lock)(struct irq_data *data);
	void		(*irq_bus_sync_unlock)(struct irq_data *data);

	void		(*irq_cpu_online)(struct irq_data *data);
	void		(*irq_cpu_offline)(struct irq_data *data);

	void		(*irq_suspend)(struct irq_data *data);
	void		(*irq_resume)(struct irq_data *data);
	void		(*irq_pm_shutdown)(struct irq_data *data);

	void		(*irq_calc_mask)(struct irq_data *data);

	void		(*irq_print_chip)(struct irq_data *data, struct seq_file *p);
	int		(*irq_request_resources)(struct irq_data *data);
	void		(*irq_release_resources)(struct irq_data *data);

	void		(*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
	void		(*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);

	int		(*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
	int		(*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);

	int		(*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);

	void		(*ipi_send_single)(struct irq_data *data, unsigned int cpu);
	void		(*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);

	unsigned long	flags;
};

稍微总结一下内核中如何处理中断:

当发生中断时,CPU跳到中断向量处执行。

首先,保存现场;之后,会跳转到第一个处理中断的入口函数(对于s3c2440是s3c24xx_handle_irq函数)。

在这个函数里面,CPU应该去读取中断寄存器,分辨发生的是哪个中断,获得硬件中断号——hwirq,然后通过hwirq可以在irq_desc数组中找到与之对应的那一项,这一项的下标叫做虚拟中断号——virq,hwirq和virq是不相同的,它们之间存在一定的偏移值

找到对应的数组项后,会调用这一数组项中的handle_irq,由handle_irq来调用对应的action成员中的handler函数,然后调用对应数组项中的irq_data.chip.irq_ack来清中断。

共享中断

假设0号中断外接了网卡和摄像头,它们是一个的关系,也就是不论是网卡还是摄像头发生了中断,最终都会触发0号中断。

 那么,irq_desc数组中对应项的action链表应该如下,第一项是irq_net,处理网卡的中断,下一项通过next相连,是irq_cam,用来处理摄像头的中断。

当检测到0号中断发生,irq_netirq_camhandler函数都会被调用,在irq_nethandler函数中判断是否是发生了网卡中断,不是的话立刻返回;同理,irq_camhandler函数也是类似的操作。

这种action链表的形式支持共享中断,对于共享中断,里面的每一个处理函数都会被执行一次,因为我们没有办法在handle_irq里面判断是哪个硬件产生了中断,这个判断过程必须交给硬件的中断处理函数来实现

以s3c2440的4号中断为例,4号中断外接了4个引脚EINT4,5,6,7,这4个引脚产生了中断,都会触发4号中断。

对于这种情况,当然也可以像irq_netirq_cam那样处理,在4号中断对应的数组项中添加4个irq_action

但是,有更好的方法。因为这种情况下,有办法分辨发生的是哪个中断(以2440为例,可以读取EINTPEND寄存器,判断发生了哪个中断),此时不需要将每个中断处理函数都调用一次。

此时就可以将handle_irq指向s3c_irq_demux函数,这是一个分发函数

  1. 在分发函数中,首先会读取寄存器,进一步分析触发了哪个中断,得到对应的hwirq';
  2. 根据hwirq'得到virq;
  3. 调用irq_desc[virq].handle_irq;

硬件中断号和虚拟中断号

插讲一下硬件中断号和虚拟中断号。

假设有CPU,中断控制器INTC,还有下一级的中断控制器subINTC。

读INTC的到hwirq,读subINTC,得到hwirq'。

将硬件中断号,加上一个偏移值,就可以得到对应的虚拟中断号(irq_desc数组对应项的下标)。

代码

讲了这么多,现在可以看代码了。

从2440处理中断的入口函数(s3c24xx_handle_irq)看起。

s3c24xx_handle_irq
    s3c24xx_handle_intc
    pnd = readl_relaxed(intc->reg_intpnd);    //读中断控制器,判断是哪个中断正在等待处理
    offset =  __ffs(pnd);             //获得hwirq
        handle_domain_irq
            __handle_domain_irq
            irq = irq_find_mapping(domain, hwirq); //根据hwirq,获得virq
            generic_handle_irq
            	struct irq_desc *desc = irq_to_desc(irq); //根据virq,获得中断描述数组的下标
                generic_handle_irq_desc
                	desc->handle_irq(desc); //调用对应的中断处理函数


总结如下:

中断处理流程:
假设中断结构如下:
sub int controller ---> int controller ---> cpu

发生中断时,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的函数清中断

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值