分类: LINUX
2014-06-04 14:56:54
原文地址:ARM Linux对中断的处理--相关数据结构 作者:tq08g2z
中断处理依赖于中断的类型:I/O中断、时钟中断和处理器间中断。
不管引起中断的电路的种类如何,所有I/O中断处理程序都执行四个相同的基本操作:
1、在内核态堆栈中保存IRQ的值和寄存器的内容。
2、为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发出中断。
3、执行共享这个IRQ的所有设备的中断服务例程。
与中断处理相关的数据结构
Linux系统里每个中断通过一个称为中断描述符的结构irq_desc来管理,各中断的信息都在这个结构中得以体现。irq_desc结构体在include/irq.h文件中定义:
struct irq_desc {
unsigned int irq;
struct timer_rand_state *timer_rand_state;
unsigned int *kstat_irqs;
#ifdef CONFIG_INTR_REMAP
struct irq_2_iommu *irq_2_iommu;
#endif
irq_flow_handler_t handle_irq;
struct irq_chip *chip;
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */
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;
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_var_t affinity;
unsigned int node;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
} ____cacheline_internodealigned_in_smp;
irq_desc结构体(中断描述符)中各个字段说明:
irq: 中断描述符的中断号
timer_rand_state: pointer to timer rand state struct
kstat_irqs: irq stats per cpu
irq_2_iommu: iommu with this irq
handle_irq: 高层的irq时间处理程序(如果为NULL,则默认调用__do_IRQ())
chip: 底层的中断硬件访问,指向PIC对象(irq_chip结构),它服务于IRQ线,Linux中断管理系统使用该成员来进行中断控制器的访问。
msi_desc: MSI descriptor
handler_data: irq_chip 方法使用的per-IRQ数据
chip_data: chip 方法使用的特定平台的per-chip 私有数据,以允许共享chip的实现
action: 标识当出现IRQ时要调用的中断服务例程。该字段指向IRQ的irqaction链表的第一个元素。我们用request_irq()注册的中断处理方法,会被用来创建相关的irqaction结构体,对于同一个中断号注册的各个中断方法会被链接在该中断号的中断描述符的该字段上。
status: 描述IRQ线状态的一组标志
depth: disable-depth用于嵌套的irq_disable()调用,如果IRQ线被激活,则显示0,如果IRQ线被禁止了不止一次,则显示一个正数。
wake_depth: enable depth, for multiple set_irq_wake() callers
irq_count: 中断计数器,统计IRQ线上发生的中断的次数(仅在诊断时使用)
last_unhandled: aging timer for unhandled count
irqs_unhandled: 对在IRQ线上发生的无法处理的中断进行计数
lock: locking for SMP。用于串行访问IRQ描述符和PIC的自旋锁。
affinity: IRQ affinity on SMP
node: node index useful for balancing
pending_mask: pending rebalanced interrupts
threads_active: number of irqaction threads currently running
wait_for_threads: wait queue for sync_irq to wait for
threaded handlers
dir: /proc/irq/ procfs入口
name: flow handler name for /proc/interrupts output
在具体的ARM SoC芯片中会有很多的中断线,每一个中断线都会用一个irq_desc结构体来描述。
如果一个中断内核没有处理,那么这个中断就是意外中断,也就是说,与某个IRQ线相关的中断处理例程(ISR)不存在,或者与某个中断线相关的所有例程都识别不出是否是自己的硬件设备发出的中断。通常,内核检查从IRQ线接收的意外中断的数量,当这条IRQ线的有故障设备没完没了的发中断时,就禁用这条IRQ线,内核不会在每监测到一个意外中断时就立刻禁用IRQ线。由于几个设备可能共享IRQ线,更合适的办法是:内核把中断和意外中断的总次数分别放在irq_desc描述符的irq_count和irqs_unhandled字段中,当第100000次中断产生时,如果意外中断的次数超过99900次内核才禁用这条IRQ线。
描述IRQ线状态的标志:
IRQ_INPROGRESS /* IRQ handler active - do not enter! */
IRQ_DISABLED /* IRQ disabled - do not enter! */
IRQ_PENDING /* IRQ pending - replay on enable */
IRQ_REPLAY /* IRQ has been replayed but not acked yet */
IRQ_AUTODETECT /* IRQ is being autodetected */
IRQ_WAITING /* IRQ not yet seen - for autodetection */
IRQ_LEVEL /* IRQ level triggered */
IRQ_MASKED /* IRQ masked - shouldn't be seen again */
IRQ_PER_CPU /* IRQ is per CPU */
IRQ_NOPROBE /* IRQ is not valid for probing */
IRQ_NOREQUEST /* IRQ cannot be requested */
IRQ_NOAUTOEN /* IRQ will not be enabled on request irq */
IRQ_WAKEUP /* IRQ triggers system wakeup */
IRQ_MOVE_PENDING /* need to re-target IRQ destination */
IRQ_NO_BALANCING /* IRQ is excluded from balancing */
IRQ_SPURIOUS_DISABLED /* IRQ was disabled by the spurious trap */
IRQ_MOVE_PCNTXT /* IRQ migration from process context */
IRQ_AFFINITY_SET /* IRQ affinity was set from userspace*/
IRQ_SUSPENDED /* IRQ has gone through suspend sequence */
IRQ_ONESHOT /* IRQ is not unmasked after hardirq */
IRQ_NESTED_THREAD /* IRQ is nested into another, no own handler thread */
irq_desc描述符的depth字段和IRQ_DISABLED标志表示IRQ线是否被禁用。每次调用disable_irq()和disable_irq_nosync()函数,depth字段的值增加,如果depth等于0,函数禁用IRQ线并设置它的IRQ_DISABLED标志。相反,每当调用enable_irq()函数,depth字段的值减少,如果depth变为0,函数激活IRQ线并清除IRQ_DISABLED标志。
Linux支持许多种类的PIC电路,如PC机的8259A、APIC等。为了以统一的方法处理所有这样的设备,Linux用了一个“PIC对象”,由PIC名字和17个标准方法组成。这种面向对象的方法的优点是,驱动程序不必关注安装在系统中的PIC种类。每个驱动程序可见的中断源透明地连接到适当的控制器。定义PIC对象的数据结构叫irq_chip。内核使用irq_chip结构的成员来完成实际的对于PIC控制器的操作,禁用中断,启用中断等。
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);
void (*ack)(unsigned int irq);
void (*mask)(unsigned int irq);
void (*mask_ack)(unsigned int irq);
void (*unmask)(unsigned int irq);
void (*eoi)(unsigned int irq);
void (*end)(unsigned int irq);
int (*set_affinity)(unsigned int irq,
const struct cpumask *dest);
int (*retrigger)(unsigned int irq);
int (*set_type)(unsigned int irq, unsigned int flow_type);
int (*set_wake)(unsigned int irq, unsigned int on);
void (*bus_lock)(unsigned int irq);
void (*bus_sync_unlock)(unsigned int irq);
/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
/*
* For compatibility, ->typename is copied into ->name.
* Will disappear.
*/
const char *typename;
};
irq_chip结构体(硬件中断芯片描述符)各个字段说明:
name: name for /proc/interrupts
startup: 启动中断(如果为NULL,则默认情况下其与->enable 相同)
shutdown: 关闭中断(如果为NULL,则默认情况下其与->disable相同)
enable: 使能中断(如果为NULL,则默认情况下其与chip->unmask相同)
disable: 禁用中断 (如果为NULL,则默认情况下其与chip->mask相同)
ack: 开始一个新的中断,即给予中断控制器以确认,以允许中断的发生
mask: 屏蔽一个中断源
mask_ack: 确认并屏蔽一个中断源
unmask: 取消屏蔽一个中断源
eoi: end of interrupt - chip level
end: end of interrupt - flow level
set_affinity: set the CPU affinity on SMP machines
retrigger: 重新发送一个IRQ给CPU
set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
set_wake: enable/disable power-management wake-on of an IRQ
bus_lock: function to lock access to slow bus (i2c) chips
bus_sync_unlock: function to sync and unlock slow bus (i2c) chips
release: release function solely used by UML
typename: obsoleted by name, kept as migration helper
多个设备能共享一个单独的IRQ。因此,内核要维护多个irqaction描述符,其中的每个描述符涉及一个特定的硬件设备和一个特定的中断。这个结构在文件include/interrupt.h中定义:
struct irqaction {
irq_handler_t handler;
unsigned long flags;
const char *name;
void *dev_id;
struct irqaction *next;
int irq;
struct proc_dir_entry *dir;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};
struct irqaction(中断行为描述符)各字段说明:
handler: 中断处理函数,指向一个I/O设备的中断服务例程。这是允许多个设备共享同一个IRQ的关键字段。
flags: 标志(参考下面的IRQF_*),描述IRQ与I/O设备之间的关系。
name: I/O设备名(通过读/proc/interrupts文件,在列出所服务的IRQ时也显示设备名)。
dev_id: I/O设备的私有数据字段。典型情况下,它标识I/O设备本身(例如,它可能等于其主设备号和此设备号),或者它指向设备驱动程序的数据
next: 共享中断情况下,指向irqaction链表的下一个元素。链表中的元素指向共享同一个IRQ的硬件设备
irq: 中断号
dir: 指向proc/irq/NN/name 入口的指针,指向proc文件系统中IRQn相关的目录项。
thread_fn: 用于线程化中断的中断处理函数
thread: 用于线程化中断的线程指针
thread_flags: 与thread 有关的标志
irqaction描述符的标志,只是被作为irq处理例程的一部分来使用的,各个标志的说明如下:
IRQF_DISABLED – 当调用action handler时保持irqs禁用
IRQF_SAMPLE_RANDOM – 设备可以被看作是事件随机的发生源,一次,内核可以用它做随机数产生器
IRQF_SHARED – 允许在多个设备间共享中断
IRQF_PROBE_SHARED - set by callers when they expect sharing
mismatches to occur
IRQF_TIMER – 标记该中断为时钟中断的标志
IRQF_PERCPU - Interrupt is per cpu
IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt
that is registered first in an shared interrupt is considered for
performance reasons)
IRQF_ONESHOT - Interrupt is not reenabled after the hardirq
handler finished.Used by threaded interrupts which need to keep the
irq line disabled until the threaded handler has been run.
最后,irq_stat数组包含NR_CPUS个元素,系统中的每个CPU对应一个元素(kernel/softirq.c文件中):
#ifndef __ARCH_IRQ_STAT
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
EXPORT_SYMBOL(irq_stat);
#endif
每个元素的类型为irq_cpustat_t,该类型包含几个计数器和内核记录CPU正在做什么的标志,该结构体的定义因体系结构而已,对于我们的ARM平台,这个结构在arch/arm/include/asm/hardirq.h文件中定义:
typedef struct {
unsigned int __softirq_pending;
unsigned int local_timer_irqs;
} ____cacheline_aligned irq_cpustat_t;
irq_cpustat_t结构的字段
__softirq_pending:表示挂起的软中断
local_timer_irqs:本地时钟中断发生的次数
在kernel/irq/handle.c中还有定义了一个全局irq_desc结构体数组,用来表示系统中的所有中断:
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.status = IRQ_DISABLED,
.chip = &no_irq_chip,
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
上面的__cacheline_aligned_in_smp在include/linux/cache.h中定义,它是一个宏,在非SMP系统中是空的:
#ifdef CONFIG_SMP
#define __cacheline_aligned_in_smp __cacheline_aligned
#else
#define __cacheline_aligned_in_smp
#endif /* CONFIG_SMP */
用于描述结构体的属性,cache行对齐。
内核用结构数组irq_desc[NR_IRQS]来管理中断处理,每一个成员对应于一个中断线。NR_IRQS是一个因平台而异的宏,对于我们的mini2440来说,该宏在arch/arm/mach-s3c2440/include/mach/irqs.h中定义:
#ifdef CONFIG_CPU_S3C2443
#define NR_IRQS (IRQ_S3C2443_AC97+1)
#else
#define NR_IRQS (IRQ_S3C2440_AC97+1)
#endif
这个宏代表平台上最大的中断号。