Linux 中断系统中的重要数据结构

Linux中断系统中的重要数据结构

可以从 request_irq(include/linux/interrupt.h) 函数一路分析得到

能弄清楚下面这个图,对Linux 中断系统的掌握也基本到位了
在这里插入图片描述
最核心的结构体是 irq_desc,之前为了易于理解,我们说在 Linux 内核中有一个中断数组,对于每一个硬件中断,都有一个数组项,这个数组就是 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		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;

**注意:**如果内核配置了 CONFIG_SPARSE_IRQ,那么它就会用基数树 radix tree 来代替 irq_desc数组。SPARSE 的意思是 “稀疏”的情况下可以用基数树来代替数组

irq_desc数组

irq_desc结构体在 include/linux/irqdesc.h 中定义,主要内容如下(简化上面):

struct irq_desc {
    // ...
	struct irq_data		irq_data;
	irq_flow_handler_t	handle_irq;
    const char		*name;
	struct irqaction	*action;	/* IRQ action list */
    // ...
} ____cacheline_internodealigned_in_smp;

每一个 irq_desc数组项中都有一个函数:handle_irq,还有一个action链表。要理解它们,需要先看中断结构图:
在这里插入图片描述
外部设备1、外部设备n共享一个GPIO中断B,多个GPIO中断汇聚到GIC(通用中断控制器)的A号中断,GIC再去中断CPU。那么软件处理时就是反过来,先读取GIC获得中断号A,再细分出GPIO 中断B,最后判断是哪一个外部芯片发生了中断。

这样看,中断处理函数有三:

1.GIC的处理函数

假设irq_desc[A].handle_irq是XXX_gpio_irq_handler(XXX 指厂家),这个函数需要读取芯片的 GPIO控制器,细分发生的是哪一个GPIO中断(假设是 B),再去调用irq_desc[B].handle_irq。

注意:irq_desc[A].handle_irq细分出中断后B,调用对应的irq_desc[B].handle_irq。

显然中断A是CPU感受到的顶层的中断,GIC中断CPU时,CPU读取GIC状态得到中断 A。

2.模块中断处理函数

比如对于GPIO模块向GIC发出的中断B,它的处理函数是irq_desc[B].handle_irq。

BSP开发人员会设置对应的处理函数,一般是 handle_level_irqhandle_edge_irq,从名字上看是用来处理电平触发的中断、边沿触发的中断。

注意:导致GPIO中断B发生的原因很多,可能是外部设备1,可能是外部设备n ,可能只是某一个 设备,也可能是多个设备。所以irq_desc[B].handle_irq会调用某个链表里的函数,这些函数由外部设备提供。这些函数自行判断该中断是否自己产生,若是则处理。

3.外部设备提供的处理函数

这里说的“外部设备”可能是芯片,也可能总是简单的按键。它们的处理函数由自己驱动程序提供,这是最熟悉这个设备的“人”:它知道如何判断设备是否发生了中断,如何处理中断。

对于共享中断,比如GPIO中断B,它的中断来源可能有多个,每个中断源对应一个中断处理函数。所以irq_desc[B]中应该有一个链表,存放着多个中断源的处理函数。

一旦程序确定发生了GPIO中断B,那么就会从链表里把那些函数取出来,一一执行。这个链表就是 action 链表。

对于上面的这个例子来说, irq_desc 数组如下:
在这里插入图片描述

irqaction结构体

irqaction 结构体在 include/linux/interrupt.h 中定义,主要内容如下

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;

当调用 request_irqrequest_threaded_irq 注册中断处理函数时,内核就会构造一个 irqaction 结构体。在里面保存 namedev_id 等,最重要的是 handlerthread_fnthread

handler是中断处理的上半部函数,用来处理紧急的事情。

thread_fn 对应一个内核线程 thread,当handler执行完毕,Linux 内核会唤醒对应的内核线程。在内核线程里,会调用 thread_fn 函数。

  • 可以提供 handler而不提供 thread_fn, 就退化为一般的 request_irq 函数
  • 可以不提供 handler 只提供 thread_fn,完全由内核线程来处理中断。
  • 也可以既提供 handler 也提供 thread_fn,这就是中断上半部、下半部。

里面还有一个名为 sedondaryirqaction 结构体,它的作用以后再分析。

在 reqeust_irq 时可以传入dev_id,为何需要 dev_id?作用有 2:

  1. 中断处理函数执行时,可以使用dev_id
  2. 卸载中断时要传入dev_id,这样才能在 action 链表中根据 dev_id 找到对应项

所以在共享中断中必须提供 dev_id,非共享中断可以不提供。

irq_data结构体

irq_data 结构体在 include/linux/irq.h 中定义,主要内容如下

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_chip指针、irq_domain指针,都是指向别的结构体。
irq_chip结构体

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);
 
	int		(*irq_nmi_setup)(struct irq_data *data);
	void		(*irq_nmi_teardown)(struct irq_data *data);
 
	unsigned long	flags;
};

irq_domain指针:

struct irq_domain {
	struct list_head link;
	const char *name;
	const struct irq_domain_ops *ops;
	void *host_data;
	unsigned int flags;
	unsigned int mapcount;
 
	/* Optional data */
	struct fwnode_handle *fwnode;
	enum irq_domain_bus_token bus_token;
	struct irq_domain_chip_generic *gc;
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
	struct dentry		*debugfs_file;
#endif
 
	/* reverse map data. The linear map gets appended to the irq_domain */
	irq_hw_number_t hwirq_max;
	unsigned int revmap_direct_max_irq;
	unsigned int revmap_size;
	struct radix_tree_root revmap_tree;
	struct mutex revmap_tree_mutex;
	unsigned int linear_revmap[];
};

irq_domain_ops结构体

struct irq_domain_ops {
	int (*match)(struct irq_domain *d, struct device_node *node,
		     enum irq_domain_bus_token bus_token);
	int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec,
		      enum irq_domain_bus_token bus_token);
	int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
	void (*unmap)(struct irq_domain *d, unsigned int virq);
	int (*xlate)(struct irq_domain *d, struct device_node *node,
		     const u32 *intspec, unsigned int intsize,
		     unsigned long *out_hwirq, unsigned int *out_type);
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	/* extended V2 interfaces to support hierarchy irq_domains */
	int (*alloc)(struct irq_domain *d, unsigned int virq,
		     unsigned int nr_irqs, void *arg);
	void (*free)(struct irq_domain *d, unsigned int virq,
		     unsigned int nr_irqs);
	int (*activate)(struct irq_domain *d, struct irq_data *irqd, bool reserve);
	void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
	int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
			 unsigned long *out_hwirq, unsigned int *out_type);
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
	void (*debug_show)(struct seq_file *m, struct irq_domain *d,
			   struct irq_data *irqd, int ind);
#endif
};

比较有意思的是irq、hwirq,irq是软件中断号,hwirq 是硬件中断号。比如上面举的例子,在GPIO 中断B是软件中断号,可以找到irq_desc[B]这个数组项;GPIO里的第x号中断,这就是 hwirq。

谁来建立irq、hwirq之间的联系呢?由irq_domain来建立。irq_domain 会把本地的hwirq映射为全局的irq,什么意思?比如GPIO控制器里有第1号中断,UART模块里也有第1号中断,这两个“第1号中断”是不一样的,它们属于不同的“域”──irq_domain。

irq_domain 结构体

irq_domain 结构体在 include/linux/irqdomain.h 中定义,主要内容如下图:
在这里插入图片描述
当我们后面从设备树讲起,如何在设备树中指定中断,设备树的中断如何被转换为 irq 时, irq_domain 将会起到极大的作为。

这里基于入门的解度简单讲讲,在设备树中你会看到这样的属性:

interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;

它表示要使用 gpio1里的第5号中断, hwirq就是5。

但是我们在驱动中会使用 request_irq(irq, handler)这样的函数来注册中断, irq是什么?它是软件中断号,它应该从“ gpio1的第5号中断”转换得来。

谁把 hwirq 转换为 irq?由 gpio1 的相关数据结构,就是 gpio1 对应的irq_domain 结构体。

irq_domain 结构体中有一个 irq_domain_ops 结构体,里面有各种操作函数,主要是:

  • xlate
    用来解析设备树的中断属性,提取出 hwirq、 type 等信息。
  • map
    把 hwirq 转换为 irq。

irq_chip 结构体

irq_chip 结构体在 include/linux/irq.h 中定义,主要内容如下
在这里插入图片描述
这个结构体跟“ chip”即芯片相关,里面各成员的作用在头文件中也列得很清楚,摘录部分如下:

* @irq_startup: start up the interrupt (defaults to ->enable if NULL)
* @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
* @irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
* @irq_disable: disable the interrupt
* @irq_ack: start of a new interrupt
* @irq_mask: mask an interrupt source
* @irq_mask_ack: ack and mask an interrupt source
* @irq_unmask: unmask an interrupt source
* @irq_eoi: end of interrupt

我们在 request_irq 后,并不需要手工去使能中断,原因就是系统调用对应的 irq_chip 里的函数帮我们使能了中断。

我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用 irq_chip 中的相关函数。

但是对于外部设备相关的清中断操作,还是需要我们自己做的。

就像上面图里的“外部设备 1“、“外部设备 n”,外设备千变万化,内核里可没有对应的清除中断操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值