linux学习中
分类: LINUX
2013-11-15 10:41:40
疑问:
1 irq_disable()和in_irq()都在何时置位,以及两者的关系
irq_disable()只是在关中断的情况下返回1,就是说Status[IE] == 0,即返回irq_disable(),而in_irq(),表示是在中断上下文中,但它并不一定是irq_disable(),
这两个函数完全没有关系。
2 为什么local_bh_disable()时要判断是否当前in_irq()。
3 中断处理程序中,中断是开的还是关的,正在处理的irq能否嵌套,irq的优先级如何。
这个依赖于驱动,本身是开的,在进入直正的中断处理函数之前一刻开,但如果isr显式要求关中断,则是关中断的。而且对于linux来说,并没有硬件软件的中断优先级。
正在执行的irq,它还会触发吗?这个依赖于不同的中断控制器,如果使用per-cpu的中断控制器,则它会屏蔽本次中断。
一 MIPS中断处理概述
中断也属于异常,如A.1所示,当一个异常(中断)发生时,如下步骤会发生
1 EPC被置为被中断的PC
2 如果中断模式是兼容模式的话(普便情况),则vector offset = 0x180.
3 如果Status[EXL] == 1, 则offset = 0x180,并且EPC啥的都不会改变
4 如果是TLB refill, 则offset = 0x0000000,如果是其它异常,则是0x180。这里强调一下是,mips R2后中断有几种模式,不同的模式它的入口还不同。这里的0x180是与R1兼容的模式。
5 如果Status[BEV] == 1, 则BASE会到0xBFC00200,否则就BASE会是0x80000000,当然在MIPS32 R2后,我们设置了EBASE寄存器,这可以让用户选择中断后跳转的指令,这与R1就不同了,它固定在0x80000000处。为了向后兼容,EBASE默认情况下,它就是0x80000000处。
6 设置cause[extcode]为异常号,中断为0。
7 Status[EXL] = 0,这个值会产生很大的影响,它会无视Status[IE, KSU]位,而强制处于内核态和关中断状态。这样做的原因是为了我们有足够的时间去保存现场。
8 最近PC会跑去base+offset的地方。
二,MIPS异常初始化
参照comcat的文档。
三 AG7240的中断初始化
在MIPS初始化过程中,它会export一些函数给目标板定义自己的驱动。对于驱动来说,它一般在arch/mips/board/irq.c里
Export的函数为arch_init_irq,这个函数由各个目标板根据自己的中断处理器等定义中断处理函数。
对于中断来讲,这里有三个概念,一个是irq_chip, 还有一个是irq_desc,中断处理函数action。
第一个表示中断控制器,
第二个表示一个中断号的描述。
第三个表示挂在这个中断号上的中断处理函数。
首先,linux预定义了NR_IRQS个中断irq_desc结构,NR_IRQS这个标志是板级相关的,在
Arch/mips/board/include/mach里面。
struct irq_desc {
unsigned int irq;
struct irq_chip *chip;
struct irqaction *action; /* IRQ action list */
}
这三个成员表示了这三个最基本的结构。
最开始,我们必须创建中断控制器的描述,并且把一个irq_desc与中断控制器联系起来。
set_irq_chip_and_handler(unsigned int irq, struct irq_chip *chip,
irq_flow_handler_t handle)
{
set_irq_chip(irq, chip);
__set_irq_handler(irq, handle, 0, NULL);
}
首先把desc与chip联系起来,再者handle挂在desc上,表示当该中断触发时,应该调用该handle。
Desc需要chip,是因为每个中断控制器都不同,电平触发,边缘触发,或者其它啥的,都需要与硬件有关系,因此使用irq_chip挂上中断控制器自己的函数,这就在irq_desc层屏蔽了具体的硬件特性。
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);
…………….
}
中断控制器虽然各有不同,但kernel已经为我们抽象了一些,它们在handle上可以一致,因此就以如下的handle可以现成使用
handle_edge_irq, edge type IRQ handler
handle_fasteoi_irq irq handler for transparent controllers
handle_level_irq Level type irq handler
handle_percpu_irq Per CPU local irq handler
我们着重讲最后一种,它是与mips cpu 中断直接相连的,它比较特殊,因为不管什么中断控制器最终都要连到mips cpu上,因为我们这里专门为它设置了一个handle。
而mips cpu对应的中断控制器为
static struct irq_chip mips_cpu_irq_controller = {
.name = "MIPS",
.ack = mask_mips_irq,
.mask = mask_mips_irq,
.mask_ack = mask_mips_irq,
.unmask = unmask_mips_irq,
.eoi = unmask_mips_irq,
};
/*
* Almost all MIPS CPUs define 8 interrupt sources. They are typically
* level triggered (i.e., cannot be cleared from CPU; must be cleared from
* device). The first two are software interrupts which we don't really
* use or support. The last one is usually the CPU timer interrupt if
* counter register is present or, for CPUs with an external FPU, by
* convention it's the FPU exception interrupt.
*
* Don't even think about using this on SMP. You have been warned.
*
* This file exports one global function:
好了,有了这些信息,我们来看AG7240是如何来注册自己的中断处理函数的。
void __init arch_init_irq(void)
{
mips_cpu_irq_init();
ar7240_misc_irq_init(AR7240_MISC_IRQ_BASE);
ar7240_gpio_irq_init(AR7240_GPIO_IRQ_BASE);
setup_irq(AR7240_CPU_IRQ_MISC, &cascade);
setup_irq(AR7240_MISC_IRQ_GPIO, &cascade);
}
首先设置mips自带的中断控制器和处理函数。这样就初始化了1-8号中断,ar7240_misc_irq_init又是初始化了10-23号中断。它使用ar7240_misc_irq_controller中断控制器,但还是使用handle_percpu_irq这个handle。
设置CPU 6号中断为cascade,表示空,因为MISC下面连接着多个模块的中断信号,因此这个最高层并不需要一个action,它只要需要分发就行。
这只是建立了一个框架,我们还真正需要为它挂上处理函数,这里还有两个工作
1 plat_irq_dispatch
这个函数是从汇编的handle_irq中跳转过来的,用来处理板极的中断。
在这个函数中,我们需要
int pending = read_c0_status() & read_c0_cause();
然后根据中断号依次调用
do_IRQ(n)。
当然,如果是MISC,我们要再读寄存器,来决定是下层的哪个中断号。
2 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
我们要为这个中断号直接的挂一个action的函数。因为最终还是会调到这个真正的中断处理函数中来。
下面来看看整个流程
NESTED(except_vec3_r4000, 0, sp) arch/mips/kernel/genex.S
.set push
.set mips3
.set noat
mfc0 k1, CP0_CAUSE
li k0, 31<<2
andi k1, k1, 0x7c
.set push
.set noreorder
.set nomacro
beq k1, k0, handle_vced
li k0, 14<<2
beq k1, k0, handle_vcei
#ifdef CONFIG_64BIT
dsll k1, k1, 1
#endif
.set pop
PTR_L k0, exception_handlers(k1)
jr k0
这个很简单了,从cause中得到当前的异常号,然后跳转,对于中断来讲,这里是handle_int。
NESTED(handle_int, PT_SIZE, sp)
SAVE_ALL
CLI
TRACE_IRQS_OFF
LONG_L s0, TI_REGS($28)
LONG_S sp, TI_REGS($28)
PTR_LA ra, ret_from_irq
j plat_irq_dispatch
END(handle_int)
这个就很清楚了吧,先保持所有,然后CLI, 这个是清EXL, 然后关中断,并且进入kernel mode。 (并不是我们必须这样,vxworks就不是这样,这里面比较tricky)。
然后跳转到plat_irq_dispatch,这也就是为什么板极包要定义这个函数的原因。
当然还有另外一个方法是,在板级包初始函数中,直接使用set_vector把整个handle_int都替换掉,这样也就不用有plat_irq_dispatch了。
不过明确,进入plat_irq_dispatch中断是关的。
do_IRQ-à generic_handle_irq-à desc->handle_irq-à handle_percpu_irq
handle_percpu_irq(unsigned int irq, struct irq_desc *desc)
{
irqreturn_t action_ret;
kstat_incr_irqs_this_cpu(irq, desc);
if (desc->chip->ack)
desc->chip->ack(irq);
action_ret = handle_IRQ_event(irq, desc->action);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
if (desc->chip->eoi)
desc->chip->eoi(irq);
}
这个函数重要,desc->chip->ack,这是给chip一个机会处理一些事情,比如说把中断信号拉低等。
对于CPU,和我们ag7240来说,有一个性质是中断信号是读过,它自动拉低,所以很简单。
那这个ack主要干什么事呢?
static inline void mask_mips_irq(unsigned int irq)
{
clear_c0_status(0x100 << (irq - MIPS_CPU_IRQ_BASE));
irq_disable_hazard();
}
表示把当前的irq给mask,这就表示当前irq在执行时,这个irq是不能再被中断的。
handle_IRQ_event
{
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();
do {
ret = action->handler(irq, action->dev_id);
}
}
这就很明白了,在中断处理函数中,如果注册中断时,要求保持关中断,则我们保持关中断,否则中断处理程序中,我们是开中断。
而且我感觉对于mips cpu的几个中断来讲,硬件是没有优先级的,软件也没有做优先级。所以它是能够被其它中断打断的。
这个和 vxworsk不同了,vxworks在软件上是做了优先级的。