Linux中断子系统分析之(二):通用的中断处理

Linux中断子系统分析之(一):整体框架
Linux中断子系统分析之(二):通用的中断处理
Linux中断子系统分析之(三):irq domain
Linux中断子系统分析之(四):驱动程序申请中断

当有外部中断发生时,硬件会根据异常向量表,找到中断的入口地址,开始调用由操作系统提供的通用中断处理函数。而系统初始化阶段的异常向量表的设置、执行中断服务例程前后的上下文保护与恢复等等,这部分是CPU架构相关代码。

通用中断处理函数,核心是围绕着struct irq_desc来展开。在linux kernel中,对于每一个外设的中断都用struct irq_desc来描述,我们称之中断描述符,保存了中断的相关信息,如硬/软件中断号、中断服务例程等。

struct irq_desc结构体定义如下:


struct irq_desc {
	......
	struct irq_data		irq_data; // 保存软件中断号、irq domain等信息
	......

	//该中断的通用处理函数
	irq_flow_handler_t	handle_irq;
	......
	struct irqaction	*action;	/* IRQ action list */
	......

#ifdef CONFIG_SPARSE_IRQ
	struct rcu_head		rcu; 
	struct kobject		kobj;
#endif
	int			parent_irq;
	struct module		*owner;
	const char		*name;
} ____cacheline_internodealigned_in_smp;

中断描述符中应该会包括底层相关的数据结构,linux kernel中把这些数据组织在一起,形成struct irq_data:

struct irq_data {
	u32			mask;
	unsigned int		irq;        //该中断的软件中断号
	unsigned long		hwirq;      //该中断的硬件中断号,仅仅限制在本中断控制器
	......
	struct irq_chip		*chip;     //该中断源连接的中断控制器
	struct irq_domain	*domain;   //用于把hwirq映射为软件中断号

#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_data		*parent_data; 
#endif
	void			*chip_data;
};

irq_chip结构体,描述一个中断控制器,定义如下:

struct irq_chip {
	struct device	*parent_device;  //指向父设备,上一级的中断控制器
	const char	*name;             //该中断控制器的name

  	//启动中断,如果设置成NULL,则默认为enable
  	unsigned int	(*irq_startup)(struct irq_data *data);

	//关闭中断,如果设置成NULL,则默认为disable
	void		(*irq_shutdown)(struct irq_data *data);

	//中断使能,如果设置成NULL,则默认为chip->unmask
	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);      //中断处理结束后调用

	//在SMP中设置CPU亲和力
	int		(*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
  
 	//重新发送中断到CPU
	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);
  
	......

	//获取中断控制器的状态
	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);

	......

	//标志位
	unsigned long	flags;
};

当发生中断后,首先获取触发中断的硬件中断号,然后通过irq domain映射为软件中断号,然后通过软件中断号就可以获取对应的中断描述符,调用中断描述符中的handler来进行中断处理。

内核中会有一个数据结构保存了关于所有IRQ的中断描述符信息。如果打开CONFIG_SPARSE_IRQ宏,中断描述符以radix-tree来组织,用户在初始化时进行动态分配,然后再插入radix-tree中,否则中断描述符以数组的形式组织,并且已经分配好。

/* kernel\irq\irqdesc.c */
#ifdef CONFIG_SYSFS
......

static RADIX_TREE(irq_desc_tree, GFP_KERNEL);

......

#else /* !CONFIG_SPARSE_IRQ */

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.handle_irq	= handle_bad_irq,
		.depth		= 1,
		.lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
	}
};

......

irq_desc的handle_irq回调函数一般是指向某个中断的通用中断处理函数,即标准中断流控函数,如handle_edge_irq 、handle_fasteoi_irq等。那么具体的外设中断服务例程在哪里呢?在irq_desc的action链表里。为什么用链表?这考虑到共享中断的问题,看下图就明白了。
在这里插入图片描述
如图有两个设备接在一条IRQ line上,设备1和设备2产生的中断,其对应的软件中断号都是一样的,也就是说irq_desc结构体的handle_irq回调函数与软件中断号一一对应。这种情况属于多个设备可以通过同一条IRQ line来共享同一个软件中断号。

irq_desc有一成员action,类型为struct irqaction:

struct irqaction {

   //指向设备特定的中断服务例程函数的指针
	irq_handler_t		handler;
	
	/* 调用handler时传给它的参数,在多个设备共享一个irq的情况下特别重要,
     设备驱动程序通过dev_id來标识自己 */
	void			*dev_id;

	.....
 
  /*指向下一个action对象,用于多个设备共享同一个irq的情形,此时action通过next构成一个链表*/
	struct irqaction	*next;

	......
} ____cacheline_internodealigned_in_smp;

在这里插入图片描述
驱动注册中断服务例程,实际上就是创建与初始化一个irqaction,然后插入irq_desc的action链表。

当发生中断时,执行irq_desc的handle_irq回调函数,回调函数会遍历action中的handler函数,进行具体的中断处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值