1.irq_desc 数组
位于include/linux/irqdesc.h
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;
......
};
如果内核配置了 CONFIG_SPARSE_IRQ,那么它就会用 基数树(radix tree) 来代替 irq_desc 数组。
SPARSE 的意思是“稀疏”,假设大小为 1000 的数组中只用到 2 个数组项,那不是浪费嘛?当中断比较“稀疏”时可以用基数树来代替数组。
有一个函数:handle_irq,还有一个 action链表。要理解它们,需要先看中断结构图:
共享中断:
- 上图一个gpio按键连接gpio模块第一个引脚1,可以设置该引脚,当电平发生变化时,让该引脚产生中断,那么gpio模块会上报中断到gic模块, gic模块继续中断cpu。
- 同理当一个外部设备网卡和该gpio按键可以共享一个中断,也接到gpio模块第一个引脚1,gpio模块会上报中断到gic模块, gic模块继续中断cpu。这里就用到了共享中断的概念。
可以看到中断的触发时从左到右的过程,那么cpu进行响应中断请求时就是从右到左的过程。
- cpu读取gic控制器,判断是A号中断,还是A'中断,如果是A号中断说明是来源于gpio模块,如果是A'中断,说明来源于其他模块。
- A号中断的来源有很多种,有gpio0,gpio1...., 又会从gpio控制寄存器来辨别倒是是哪一个gpio产生的中断,比如是B号中断
- B号中断的来源有很多种,有按键,网卡...
1.1 中断处理函数handle_irq
中断的处理函数来源有三:
-
GIC 的处理函数:
GIC 中断 CPU 时,CPU 读取 GIC 状态得到中断 A。假设 irq_desc[A].handle_irq 是 XXX_gpio_irq_handler(XXX 指厂家),这个函数需要读取芯片的 GPIO 控制器,细分发生的是哪一个 GPIO 中断(假设是B),再去调用 irq_desc[B]. handle_irq。 -
模块的中断处理函数:
对于 GPIO 模块向 GIC 发出的中断 B , 它 的 处 理 函 数 是irq_desc[B].handle_irq。
导致 GPIO 中断 B 发生的原因很多,可能是外部设备 1,可能是外部设备n,可能只是某一个设备,也可能是多个设备。所以 irq_desc[B].handle_irq会调用链表里的函数,这些函数由外部设备提供。这些函数自行判断该中断是否自己产生,若是则处理。 -
外部设备提供的处理函数:(也就是action里面的函数)
这里说的“外部设备”可能是芯片,也可能总是简单的按键。它们的处理函数由自己驱动程序提供。对于共享中断,比如 GPIO 中断 B,它的中断来源可能有多个,每个中断源对应一个中断处理函数。所以 irq_desc[B]中应该有一个链表, 这个链表就是 action 链表。一旦程序确定发生了 GPIO 中断 B,那么就会从链表里把那些函数取出来,一一执行。
1.2 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;
irq_desc[A]这里对应的action一般为NULL, 而irq_desc[B]的handle_irq会调用链表里的函数,这些函数就是对应不同的irqaction。
当调用 request_irq、request_threaded_irq 注册中断处理函数时,内核就会构造一个 irqaction 结构体。在里面保存 name、dev_id 等,最重要的是 handler、thread_fn、thread。
函数原型为:
函数原型
extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags, const char *name, void *dev);
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags, devname, dev_id);
}
这里irq编号使用的虚拟中断号,虚拟中断号怎么来?后面使用在做讲解。
handler :是中断处理的上半部函数,用来处理紧急的事情。
thread_fn :对应一个内核线程 thread,当 handler 执行完毕,Linux 内核会唤醒对应的内核线程。在内核线程里,会调用 thread_fn 函数。
- 可以提供 handler 而不提供 thread_fn,就退化为一般的 request_irq 函数。
- 可以不提供 handler 只提供 thread_fn,完全由内核线程来处理中断。
- 也可以既提供 handler 也提供 thread_fn,这就是中断上半部、下半部。
在 reqeust_irq 时可以传入 dev_id,为何需要 dev_id?作用有 2:
- 中断处理函数执行时,可以使用 dev_id
- 卸载中断时要传入 dev_id,这样才能在 action 链表中根据 dev_id 找到对应项(所以在共享中断中必须提供 dev_id,非共享中断可以不提供)
1.3 irq_data
定义再include/linux/irq.h
/**
* struct irq_data - per irq chip data passed down to chip functions
* @mask: precomputed bitmask for accessing the chip registers
* @irq: interrupt number
* @hwirq: hardware interrupt number, local to the interrupt domain
* @common: point to data shared by all irqchips
* @chip: low level interrupt hardware access
* @domain: Interrupt translation domain; responsible for mapping
* between hwirq number and linux irq number.
* @parent_data: pointer to parent struct irq_data to support hierarchy
* irq_domain
* @chip_data: platform-specific per-chip private data for the chip
* methods, to allow shared chip implementations
*/
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就是个中转站,里面有 irq_chip 指针 irq_domain 指针,irq 是软件中断号,hwirq 是硬件中断号。
比如GPIO 中断 B 就是软件中断号,可以找到 irq_desc[B]这个数组项;GPIO 里的第 x 号中断,这就是 hwirq。
irq、hwirq 之间的联系呢?由 irq_domain 来建立。下面介绍irq_domain
1.3 irq_domain
include/linux/irqdomain.h 中定义该结构。
/**
* struct irq_domain - Hardware interrupt number translation object
* @link: Element in global irq_domain list.
* @name: Name of interrupt domain
* @ops: pointer to irq_domain methods
* @host_data: private data pointer for use by owner. Not touched by irq_domain
* core code.
* @flags: host per irq_domain flags
* @mapcount: The number of mapped interrupts
*
* Optional elements
* @fwnode: Pointer to firmware node associated with the irq_domain. Pretty easy
* to swap it for the of_node via the irq_domain_get_of_node accessor
* @gc: Pointer to a list of generic chips. There is a helper function for
* setting up one or more generic chips for interrupt controllers
* drivers using the generic chip library which uses this pointer.
* @parent: Pointer to parent irq_domain to support hierarchy irq_domains
* @debugfs_file: dentry for the domain debugfs file
*
* Revmap data, used internally by irq_domain
* @revmap_direct_max_irq: The largest hwirq that can be set for controllers that
* support direct mapping
* @revmap_size: Size of the linear map table @linear_revmap[]
* @revmap_tree: Radix map tree for hwirqs that don't fit in the linear map
* @linear_revmap: Linear table of hwirq->virq reverse mappings
*/
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[];
};
设备树中你会看到这样的属性:
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;
表示使用gpio1_5作为中断,hwirq 就是 5。当我们在驱动中会使用 request_irq(irq, handler)这样的函数来注册中断,irq编号就是虚拟中断,那么虚拟中断号(软件中断号)要怎么得到?
就是 gpio1 对应的irq_domain 结构体。irq_domain 结构体中有一个 irq_domain_ops 结构体,里面有各种操作函
数,主要是:
/**
* struct irq_domain_ops - Methods for irq_domain objects
* @match: Match an interrupt controller device node to a host, returns
* 1 on a match
* @map: Create or update a mapping between a virtual irq number and a hw
* irq number. This is called only once for a given mapping.
* @unmap: Dispose of such a mapping
* @xlate: Given a device tree node and interrupt specifier, decode
* the hardware irq number and linux irq type value.
*
* Functions below are provided by the driver and called whenever a new mapping
* is created or an old mapping is disposed. The driver can then proceed to
* whatever internal data structures management is required. It also needs
* to setup the irq_desc when returning from map().
*/
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
};
- xlate
用来解析设备树的中断属性,提取出 hwirq、type 等信息。 - map
把 hwirq 转换为 irq。
1.4 irq_chip
irq_chip 结构体在 include/linux/irq.h 中定义
/**
* struct irq_chip - hardware interrupt chip descriptor
*
* @parent_device: pointer to parent device for irqchip
* @name: name for /proc/interrupts
* @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
* @irq_set_affinity: Set the CPU affinity on SMP machines. If the force
* argument is true, it tells the driver to
* unconditionally apply the affinity setting. Sanity
* checks against the supplied affinity mask are not
* required. This is used for CPU hotplug where the
* target CPU is not yet set in the cpu_online_mask.
* @irq_retrigger: resend an IRQ to the CPU
* @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
* @irq_set_wake: enable/disable power-management wake-on of an IRQ
* @irq_bus_lock: function to lock access to slow bus (i2c) chips
* @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
* @irq_cpu_online: configure an interrupt source for a secondary CPU
* @irq_cpu_offline: un-configure an interrupt source for a secondary CPU
* @irq_suspend: function called from core code on suspend once per
* chip, when one or more interrupts are installed
* @irq_resume: function called from core code on resume once per chip,
* when one ore more interrupts are installed
* @irq_pm_shutdown: function called from core code on shutdown once per chip
* @irq_calc_mask: Optional function to set irq_data.mask for special cases
* @irq_print_chip: optional to print special chip info in show_interrupts
* @irq_request_resources: optional to request resources before calling
* any other callback related to this irq
* @irq_release_resources: optional to release resources acquired with
* irq_request_resources
* @irq_compose_msi_msg: optional to compose message content for MSI
* @irq_write_msi_msg: optional to write message content for MSI
* @irq_get_irqchip_state: return the internal state of an interrupt
* @irq_set_irqchip_state: set the internal state of a interrupt
* @irq_set_vcpu_affinity: optional to target a vCPU in a virtual machine
* @ipi_send_single: send a single IPI to destination cpus
* @ipi_send_mask: send an IPI to destination cpus in cpumask
* @irq_nmi_setup: function called from core code before enabling an NMI
* @irq_nmi_teardown: function called from core code after disabling an NMI
* @flags: chip specific flags
*/
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;
};
我们在 request_irq 后,并不需要手工去使能中断,原因就是系统调用对应的 irq_chip 里的irq_enable函数帮我们使能了中断。
我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用 irq_chip 中的相关函数。
但是对于外部设备相关的清中断操作,还是需要我们自己做的。就像上面图里的“外部设备 1“、“外部设备 n”,外设备千变万化,内核里没有对应的清除中断操作。