分类: LINUX
2013-05-06 18:21:31
中断子系统分析(二)
上一篇中分析了中断产生时系统响应中断的流程,现在我们来看一下中断子系统在内核中的几个重要的组成部分。
我们知道,内核中的每个子系统都是由几个重要的结构体组成的,每个结构体都代表着不同的对象,它们之间会通过一定的方式联系起来。在中断子系统中我们需要了解三个比较重要的结构体:struct irq_desc,struct irqaction,struct irq_chip。下面一一来分析这几个结构体。
中断描述符:
struct irq_desc {
struct irq_data irq_data; //在这个结构体变量当中保存了一些很重要的结构体成员变量:如中断控制器指针struct irq_chip,中断号irq等
struct timer_rand_state *timer_rand_state;//伪随机数状态指针,我们知道有些中断是随机产生的,因此我们可以通过对这些中断产生的时间来获取随机数。
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 *///禁止或者使能中断嵌套管理标志,当调用enable_irq()时该值减1,当调用disable_irq()时该值加1,根据enable_irq的代码可知:
switch (desc->depth) {
case 0:
err_out:
WARN(1, KERN_WARNING "Unbalanced enable for IRQ %d\n", irq);
break;
case 1: {
if (desc->istate & IRQS_SUSPENDED)
goto err_out;
/* Prevent probing on this irq: */
irq_settings_set_noprobe(desc);
irq_enable(desc);
check_irq_resend(desc, irq);
/* fall-through */
}
default:
desc->depth--;
}
只有在depth为1时才会真正的去让中断控制器使能中断。同样看disable_irq代码:
if (!desc->depth++)
irq_disable(desc);
只有在depth为0时才真正通过中断控制器去关闭中断。
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;
raw_spinlock_t lock;
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_PROC_FS
struct proc_dir_entry *dir;//指向/proc/irq/ 目录
#endif
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
中断服务例程描述符:
struct irqaction {
irq_handler_t handler;//中断服务例程,中断产生的原因是因为设备需要处理一些事务,最紧急的事务就是在中断服务例程当中处理。
unsigned long flags;//中断标志
#define IRQF_DISABLED 0x00000020 表明该中断是一个快速中断,在中断服务例程处理过程中屏蔽其他中断
#define IRQF_SAMPLE_RANDOM 0x00000040 表明将中断作为一个随机源
#define IRQF_SHARED 0x00000080 用于共享中断,使用该标志时在注册中断时必须传递最后一个参数void *dev,用于区别到底是哪个设备产生了中断。
#define IRQF_PROBE_SHARED 0x00000100
#define __IRQF_TIMER 0x00000200 专用于定时器中断
#define IRQF_PERCPU 0x00000400 用于SMP系统当中,指定某CPU处理该中断
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000 表明中断是不可重入的,也就是说当系统还在处理当前中断的时候,该中断线不能再产生中断
#define IRQF_NO_SUSPEND 0x00004000
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next; //在共享中断中指向下一个需要处理的中断服务例程。
int irq; 中断号
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
中断控制器描述符:
struct irq_chip {
const char *name;//在/proc/interrupts中显示中断控制器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);//用于CPU对irq的回应,表示希望清除未处理中断,准备接受下一个中断
void (*irq_mask)(struct irq_data *data);//屏蔽中断,与disable_irq有区别,disable_irq表示中断被关闭了,中断控制器无法接收到中断线传递中断过来,mask表示中断控制器还可以接收到中断,只是不将中断信号传递给CPU
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);//CPU处理完中断后向中断控制器发送eoi信号
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);//用来设置CPU对中断的处理权限,使该中断被指定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);//设置该中断是否可作为系统的唤醒源。
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_print_chip)(struct irq_data *data, struct seq_file *p);
unsigned long flags;
/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
};
以上是中断子系统三个比较重要的结构体的描述。
我们知道,在一般的设备驱动程序当中,设备在初始化的时候会对中断进行初始化,中断的初始化很简单,就是向系统申请注册一个中断
int 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);
}
第一个参数是硬件中断号,在主板的gpio口中有一个gpio对应着设备的中断,根据平台的不同,硬件中断号与gpio引脚号一致或者有一个特定的转换关系。如在我的系统当中,TP的中断线对应gpio口是17:
而注册时使用的硬件中断号为:
第二个参数是中断服务例程,上一篇中我们分析了当中断产生时最终会执行对应设备的中断服务程序。
第三个参数是中断的标志位。如果希望系统在处理中断服务程序的时候暂时关闭其他中断,那么就设置标志IRQF_DISABLED,将中断线设为共享时就需要设置标志IRQF_SHARED,还可以设置中断的触发方式,如将中断设置为上升沿或者下降沿触发就设置IRQF_TRIGGER_RISING或者IRQF_TRIGGER_FALLING。
第四个参数是中断名,可以系统的/proc/interrupts目录下看到注册的该中断。
第五个参数在中断线被共享时用到,用来给中断服务程序传递参数。如果中断线被共享,那么当共享的某个设备产生了中断,所有共享这条中断线的设备服务程序都将运行,那么就要去区别到底是其中的哪个设备产生了中断,这样才能不对未共享中断线而又未产生中断的设备造成影响。我们经常会在驱动程序中将代表着设备本身的结构作为第五个参数传递给中断服务程序,这样当中断服务驱动运行的时候首先会根据这个参数来判断是不是自己产生了中断,如果是则处理,如果不是就什么也不做。
下面我们来分析注册中断的过程:
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);
}
Request_irq会直接调用request_threaded_irq():
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
/*
* Sanity-check: shared interrupts must pass in a real dev-ID,
* otherwise we'll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc).
*/
if ((irqflags & IRQF_SHARED) && !dev_id)
return -EINVAL;
//从这里可以看到,如果我们设置了中断共享标志,但是又没有传递第五个参数去区分中断源,就会返回-EINVAL
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
//根据中断号从中断描述符集合中获取对应中断号的中断描述符。
if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
//handler或者thread_fn必须要有一个,如果中断服务程序没有就使用默认的中断处理程序
}
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
//这里为中断服务程序action分配内存并且根据参数初始化好它
chip_bus_lock(desc);
retval = __setup_irq(irq, desc, action);//将初始化好的中断服务程序action传递给__setup_irq()
chip_bus_sync_unlock(desc);
if (retval)
kfree(action);
…….
}
接着分析static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new){
struct irqaction *old, **old_ptr;
const char *old_name = NULL;
unsigned long flags, thread_mask = 0;
int ret, nested, shared = 0;
cpumask_var_t mask;
if (!desc)
return -EINVAL;
if (desc->irq_data.chip == &no_irq_chip)
return -ENOSYS;
//中断描述符必须指向一个初始化好的中断控制器,这样才能使注册的中断正常工作。
if (!try_module_get(desc->owner))
return -ENODEV;
if (new->flags & IRQF_SAMPLE_RANDOM) {
rand_initialize_irq(irq);//如果中断作为一个随机数源,则初始化它
}
…….
/*
* The following block of code has to be executed atomically
*/
raw_spin_lock_irqsave(&desc->lock, flags);
old_ptr = &desc->action;
old = *old_ptr;
if (old) {
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||
((old->flags ^ new->flags) & IRQF_ONESHOT)) {
old_name = old->name;
goto mismatch;
}
//old不为空,即desc->action不为空,也就是说这次注册的irq所对应的中断线已经被注册过(如果没有中断描述符desc的中断服务例程action就为NULL了),这时候就要查看两次注册irq时是否都设置了IRQF_SHARED,中断触发方式是否一致,IRQF_ONESHOT和IRQF_PERCPU是否一致。
/* All handlers must agree on per-cpuness */
if ((old->flags & IRQF_PERCPU) !=
(new->flags & IRQF_PERCPU))
goto mismatch;
/* add new interrupt at end of irq queue */
do {
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
//找到action链表上的最后一个action并保存它,且将shared变量置1,表明这是一个共享中断的注册
shared = 1;
}
if (new->flags & IRQF_ONESHOT) {
/*
* Unlikely to have 32 resp 64 irqs sharing one line,
* but who knows.
*/
if (thread_mask == ~0UL) {
ret = -EBUSY;
goto out_mask;
}
new->thread_mask = 1 << ffz(thread_mask);
}
//如果中断线被共享,在上面有对共享该中断的服务程序的thread_mask的保存,设置了IRQF_ONESHOT表明中断不可重入,如果共享该中断线的其他中断处理函数正在处理,则返回中断线忙。
if (!shared) {
init_waitqueue_head(&desc->wait_for_threads);
/* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) {
ret = __irq_set_trigger(desc, irq,
new->flags & IRQF_TRIGGER_MASK);
//设置中断触发条件
if (ret)
goto out_mask;
}
desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
IRQS_ONESHOT | IRQS_WAITING);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
if (new->flags & IRQF_PERCPU) {
irqd_set(&desc->irq_data, IRQD_PER_CPU);
irq_settings_set_per_cpu(desc);
}
if (new->flags & IRQF_ONESHOT)
desc->istate |= IRQS_ONESHOT;
if (irq_settings_can_autoenable(desc))
irq_startup(desc, true);
//假如没有置_IRQ_NOAUTOEN标志,则开启中断,并将depth置0,表明irq已经被打开
else
/* Undo nested disables: */
desc->depth = 1;
/* Exclude IRQ from balancing if requested */
if (new->flags & IRQF_NOBALANCING) {
irq_settings_set_no_balancing(desc);
irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
}
/* Set default affinity mask once everything is setup */
setup_affinity(irq, desc, mask);
} else if (new->flags & IRQF_TRIGGER_MASK) {
unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;
unsigned int omsk = irq_settings_get_trigger_mask(desc);
if (nmsk != omsk)
/* hope the handler works with current trigger mode */
pr_warning("IRQ %d uses trigger mode %u; requested %u\n",
irq, nmsk, omsk);
}
new->irq = irq;//将中断号保存在中断服务程序描述符结构当中
*old_ptr = new;//将新创建且初始化好的中断服务程序挂到中断描述符desc的action链表尾上,这样中断描述符便与中断服务例程关联起来了。
/* Reset broken irq detection when installing new handler */
desc->irq_count = 0;
desc->irqs_unhandled = 0;
…….
if (new->thread)
wake_up_process(new->thread);
//如果有中断线程,则唤醒它
register_irq_proc(irq, desc);
new->dir = NULL;
register_handler_proc(irq, new);
//注册相关/proc文件
free_cpumask_var(mask);
return 0;
…….
}
根据以上对注册中断request_irq()的流程分析我们可以发现,注册一个中断的主要工作就是为中断irq准备好一个中断服务例程action,以便在中断到来的时候可以对设备进行处理。
上一篇的分析中我们知道当中断产生后系统最终会去执行desc->handle_irq(irq, desc)也就是执行中断描述符的handle_irq指针,而并不是它的中断服务程序链表中的action。这又是为什么呢?这还得从中断控制器的初始化说起了。
在main.c的start_kernel()中有init_IRQ(){
Machine->desc->init_irq();
}
也就是调用板文件中定义的对中断控制器的初始化接口:
MACHIE_START(EMEDKB,“PXA988”)
.map_io=mmp_map_io,
.init_early=pxa988_init_early,
.init_irq=pxa988_init_irq,
.timer=&pxa988_timer,
.reserve=pxa988_reserve,
.handle_irq=gic_handle_irq,
.init_machine=emeidkb_init_common,
.restart=mmp_arch_reset,
MACHINE_END
pxa988_init_irq()中的代码大多是跟平台相关的,主要是初始化好中断控制器,平台使用通用中断子系统来管理中断控制器,其主要代码在gic.c当中,其中有对中断描述符的初始化:
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());一路跟进去,最终它会调用
static int alloc_descs(unsigned int start, unsigned int cnt, int node,
struct module *owner)
{
struct irq_desc *desc;
int i;
for (i = 0; i < cnt; i++) {
desc = alloc_desc(start + i, node, owner);
/*在这里为每一个中断分配一个中断描述符,并且使用传递的参数和内核默认的配置初始化好这个中断描述符:
static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
struct module *owner)
{
int cpu;
desc->irq_data.irq = irq;
desc->irq_data.chip = &no_irq_chip;
desc->irq_data.chip_data = NULL;
desc->irq_data.handler_data = NULL;
desc->irq_data.msi_desc = NULL;
irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
desc->handle_irq = handle_bad_irq;
desc->depth = 1;
desc->irq_count = 0;
desc->irqs_unhandled = 0;
desc->name = NULL;
desc->owner = owner;
for_each_possible_cpu(cpu)
*per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
desc_smp_init(desc, node);
}
这里主要初始化了中断描述符的irq_data域,并且将irq_desc的中断控制器信息chip初始化为no_irq_chip,也就是指向一个无实际意义的抽象的中断控制器。
*/
if (!desc)
goto err;
mutex_lock(&sparse_irq_lock);
irq_insert_desc(start + i, desc);
/*
将初始化好的中断描述符插入到基数树中去(radix tree)或者irq_desc数组当中
内核使用两种方式来保存中断描述符信息:数组和基数树
数组:
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),
}
};
采用数组方式来保存中断描述符时,分配一个NR_IRQS个成员的数组,当需要获取irq对应的irq_desc时,只要拿到这个数组的第irq号元素即可:
struct irq_desc *irq_to_desc(unsigned int irq)
{
return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}
这种方式比较简单,但当中断号不连续时需要浪费部分内存。
基数树方式:
将新创建的irq_desc插入到一棵基数树上,中断号irq作为其在基数树上的索引,因此当我们需要irq对应的irq_desc,可以通过其irq号从基数树上拿到irq_desc。使用基数树方式的主要好处可以动态的分配irq_desc,比较节约资源。
*/
mutex_unlock(&sparse_irq_lock);
}
return start;
…….
}
现在,中断控制器上所有支持的中断的中断描述符都初始化好了,但是这些irq_desc还没有与中断控制器关联起来(此时irq_desc的irq_data中保存的chip还是no_irq_chip)。
平台在gic.c中定义了一个操作集:
Const struct irq_domain_ops gic_irq_domain_ops = {
.map=gic_irq_domain_map,
.gic_irq_domain_xlate,
};
还定义初始化了一个中断控制器irq_chip:
static struct irq_chip gic_chip = {
.name = "GIC",
.irq_mask = gic_mask_irq,
.irq_unmask = gic_unmask_irq,
.irq_eoi = gic_eoi_irq,
.irq_set_type = gic_set_type,
.irq_retrigger = gic_retrigger,
#ifdef CONFIG_SMP
.irq_set_affinity = gic_set_affinity,
#endif
.irq_set_wake = gic_set_wake,
};
在初始化了irq_desc之后,接着会调用上面操作集的map去将中断描述符和中断控制器关联起来:
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
if (hw < 32) {
irq_set_percpu_devid(irq);
irq_set_chip_and_handler(irq, &gic_chip,
handle_percpu_devid_irq);
set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
} else {
irq_set_chip_and_handler(irq, &gic_chip,
handle_fasteoi_irq);
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
}
irq_set_chip_data(irq, d->host_data);
return 0;
}
通过irq_set_chip_and_handler()将中断描述符与中断控制器关联起来,以及初始化好handle_irq指针:
irq_set_chip(irq, chip);// desc->irq_data.chip = chip;
__irq_set_handler(irq, handle, 0, name);// desc->handle_irq = handle;
即desc->handle_irq= handle_fasteoi_irq(以它为例)
handle_fasteoi_irq()- >handle_irq_event()->handle_irq_event_percpu():
do {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id);//在这里终于看到了中断服务例程的执行了。
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
irq, action->handler))
local_irq_disable();
…….
retval |= res;
action = action->next;
} while (action);
到这里我们可以看到,在开机启动的时候,系统就初始化好了中断控制器所支持的中断线的中断描述符,并关联好了中断控制器与中断描述符。我们在设备驱动中根据中断号向系统注册中断,实现好中断服务程序,系统就可以根据中断号将中断服务程序和中断描述符关联起来,因此在中断产生的时候,系统就可以根据中断号去执行相应的中断服务程序了。