引入几个Linux中断子系统下重要数据结构:
1、虚拟中断号virq和硬件中断号hwirq;
2、中断域:irq_domain;
3、中断描述符:irq_desc;
4、中断处理:struct irqaction;
理解这几个数据结构以及之间的关系对于理解Linux中断子系统的构成非常重要!
在描述hwirq转换为virq时, 引入一个概念:irq_domain域,在这个域里hwirq转换为virq。显然irq_domain和中断控制器是一一对应的,每一个中断控制器对应一个irq_domain。
【提出一个问题:怎样使用irq_domain把硬件中断号转化为软件中断号?】
以前中断号(virq)跟硬件密切相关: 对于只有少数中断控制器来说这种方法是比较好的,给出硬件中断号预先确定好它的虚拟中断号,但是当中断控制器数量变多时,实际的中断号就有可能成百上千或者更多,这样操作就会比较麻烦了。
解决办法:virq和hwirq之间的联系取消掉, hwirq仅仅是一个标号而已:也就是虚拟中断号和硬件的固定联系取消掉,当我们需要使用某一个硬件中断时,在irq_desc[]查找一个空缺的项,这个空缺的项的下标就是虚拟中断号。
<include/linux/irqdesc.h>
/**
* struct irq_desc - interrupt descriptor
* @irq_common_data: per irq and chip data passed down to chip functions
* @kstat_irqs: irq stats per cpu
* @handle_irq: highlevel irq-events handler
* @preflow_handler: handler called before the flow handler (currently used by sparc)
* @action: the irq action chain
* @status: status information
* @core_internal_state__do_not_mess_with_it: core internal status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple irq_set_irq_wake() callers
* @irq_count: stats field to detect stalled irqs
* @last_unhandled: aging timer for unhandled count
* @irqs_unhandled: stats field for spurious unhandled interrupts
* @threads_handled: stats field for deferred spurious detection of threaded handlers
* @threads_handled_last: comparator field for deferred spurious detection of theraded handlers
* @lock: locking for SMP
* @affinity_hint: hint to user space for preferred irq affinity
* @affinity_notify: context for notification of affinity changes
* @pending_mask: pending rebalanced interrupts
* @threads_oneshot: bitfield to handle shared oneshot threads
* @threads_active: number of irqaction threads currently running
* @wait_for_threads: wait queue for sync_irq to wait for threaded handlers
* @nr_actions: number of installed actions on this descriptor
* @no_suspend_depth: number of irqactions on a irq descriptor with
* IRQF_NO_SUSPEND set
* @force_resume_depth: number of irqactions on a irq descriptor with
* IRQF_FORCE_RESUME set
* @dir: /proc/irq/ procfs entry
* @name: flow handler name for /proc/interrupts output
*/
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data; /* 重要,irq_chip管理结构体 */
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq; /* 重要,中断流控层处理,调用ISR */
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* 重要,request_irq注册的中断ISR */
unsigned int status_use_accessors; /* 重要,知识irq_data的状态信息 */
unsigned int core_internal_state__do_not_mess_with_it; /* 重要,即istate,指示当前desc的状态信息 */
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; /* 重要,SMP下同步 */
struct cpumask *percpu_enabled;
#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
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
/*
* struct irq_desc结构体大部分成员都是辅助性的,关键的成员是irq_data、handle_irqs、action、depth、lock、istat。
* irq_desc[]数组的初始化,就是其主要成员的初始化的过程,在这里做简单的说明:
*
* 1)action指针指向具体的设备驱动提供的中断处理操作,就是所谓的ISR。
* action本身是一个单向链表结构体,由next指针指向下一个操作,因此action实际上是一个操作链,可以用于共享IRQ线的情况。
* 2)handle_irq是irq_desc结构中与PIC相关的中断处理函数的接口,通常称作”hard irq handler“。
* 此函数对应了PIC中的handle_xxx_irq()系列函数(xxx代表触发方式),do_IRQ()就会调用该函数,此函数最终会执行desc->action。
* 3)irq_data用于描述PIC方法使用的数据,irq_data下面有两个比较重要的结构:chip和state_use_accessors。
* 前者表示此irq_desc[]元素时用的PIC芯片类型,其中包含对该芯片的基本操作方法的指针;后者表示该chip的状态和属性,其中有些用于判断irq_desc本身应该所处的状态。
* 4)lock用于SMP下不同core下的同步。
* 5)depth表示中断嵌套深度,也即一个中断打断了几个其他中断。
* 6)istate表示该desc目前的状态。
* */
<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;
};
/**
* struct irq_chip - hardware interrupt chip descriptor
*
* @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
* @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
* @flags: chip specific flags
*/
struct irq_chip {
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);
unsigned long flags;
};
<include/linux/interrupt.h>
/**
* struct irqaction - per interrupt action descriptor
* @handler: interrupt handler function
* @name: name of the device
* @dev_id: cookie to identify the device
* @percpu_dev_id: cookie to identify the device
* @next: pointer to the next irqaction for shared interrupts
* @irq: interrupt number
* @flags: flags (see IRQF_* above)
* @thread_fn: interrupt handler function for threaded interrupts
* @thread: thread pointer for threaded interrupts
* @secondary: pointer to secondary irqaction (force threading)
* @thread_flags: flags related to @thread
* @thread_mask: bitmask for keeping track of @thread activity
* @dir: pointer to the proc/irq/NN/name entry
*/
struct irqaction {
irq_handler_t handler; /* primary handler */
void *dev_id; /* 用于共享中断-设备号 */
void __percpu *percpu_dev_id;
struct irqaction *next; /* 用于共享中断,指向下一个中断处理 */
irq_handler_t thread_fn; /* threaded handler */
struct task_struct *thread; /* 指向所创建的内核线程 */
struct irqaction *secondary; /* 用于中断处理强制线程化,将primary handler设置为thread handler */
unsigned int irq; /* 虚拟中断号,对应中断描述符desc */
unsigned int flags; /* 用于描述handler的特性,其前缀为IRQF_,表示IRQ Flags */
unsigned long thread_flags; /* 用于描述thread_fn的特性,其前缀为IRQTF_,表示IRQ Thread Flags */
unsigned long thread_mask; /* 用于共享中断 */
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
<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
*
* Optional elements
* @of_node: Pointer to device tree nodes associated with the irq_domain. Used
* when decoding device tree interrupt specifiers.
* @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
*
* 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;
/* 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
/* 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;
unsigned int linear_revmap[]; //保存hwirq对应的virq,譬如:linear_revmap[hwirq] = virq
};
上述数据结构体之间的关系如下图所示: