linux的中断机制

 

linux混混之牢骚:

温州老板借贷跑了?怨谁?他妈的怨他妈的谁?带都的找领导批条子,批你***,钱都给了有后台的了,还装模作样的紧缩中小型公司贷款,怨他妈的谁?日~~~~

 

linux version: 2.6.32

 

linux中断之数据结构:

irq_desc结构:

/**
 * struct irq_desc - interrupt descriptor
 * @irq:		interrupt number for this descriptor
 * @timer_rand_state:	pointer to timer rand state struct
 * @kstat_irqs:		irq stats per cpu
 * @irq_2_iommu:	iommu with this irq
 * @handle_irq:		highlevel irq-events handler [if NULL, __do_IRQ()]
 * @chip:		low level interrupt hardware access
 * @msi_desc:		MSI descriptor
 * @handler_data:	per-IRQ data for the irq_chip methods
 * @chip_data:		platform-specific per-chip private data for the chip
 *			methods, to allow shared chip implementations
 * @action:		the irq action chain
 * @status:		status information
 * @depth:		disable-depth, for nested irq_disable() calls
 * @wake_depth:		enable depth, for multiple 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
 * @lock:		locking for SMP
 * @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 entry
 * @name:		flow handler name for /proc/interrupts output
 */
struct irq_desc {
	unsigned int		irq;              //产生中断的中断号,有个irq_to_desc函数可以通过中断号找到对应的irq_desc结构。
	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 */       //比较重要的结构,action list
	unsigned int		status;		/* IRQ status */

	unsigned int		depth;		/* nested irq disables */  //关中断的次数,关闭一次depth++,当打开一次depth--,当减到0时,则可以打开中断。
	unsigned int		wake_depth;	/* nested wake enables */   //唤醒次数
	unsigned int		irq_count;	/* For detecting broken IRQs */  //所有的中断次数,在note_interrupt 函数中,每100000次清零一次
	unsigned long		last_unhandled;	/* Aging timer for unhandled count */  //记录时间,上次处理的时候
	unsigned int		irqs_unhandled;   //没有处理过的irq的次数。 这三个变量都在note_interrupt中处理。
	raw_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_desc结构。中断号作为数据的下标 即可找到对应的irq_desc。irq_desc结构的数组在internals.h中定义。

举例介绍: 在omap beagle board中是这么定义irq_desc的。arch/arm/mach-omap2/irq.c-----void omap_init_irq

void __init omap_init_irq(void)
{
	unsigned long nr_of_irqs = 0;
	unsigned int nr_banks = 0;
	int i;
	......................
	for (i = 0; i < nr_of_irqs; i++) {
		set_irq_chip(i, &omap_irq_chip);	
		set_irq_handler(i, handle_level_irq);
		set_irq_flags(i, IRQF_VALID);
	}
}

这个循环对所有中断的irq_desc结构进行赋值。   对结构的:struct irq_chip *chip; ,irq_flow_handler_t handle_irq;和unsigned int status; /* IRQ status */进行赋值。

irq_chip结构:

/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @name:		name for /proc/interrupts
 * @startup:		start up the interrupt (defaults to ->enable if NULL)
 * @shutdown:		shut down the interrupt (defaults to ->disable if NULL)
 * @enable:		enable the interrupt (defaults to chip->unmask if NULL)
 * @disable:		disable the interrupt (defaults to chip->mask if NULL)
 * @ack:		start of a new interrupt
 * @mask:		mask an interrupt source
 * @mask_ack:		ack and mask an interrupt source
 * @unmask:		unmask an interrupt source
 * @eoi:		end of interrupt - chip level
 * @end:		end of interrupt - flow level
 * @set_affinity:	set the CPU affinity on SMP machines
 * @retrigger:		resend an IRQ to the 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
 */
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主要是mask interrupt,unmaks interrupt等一些寄存器操作。 可参考:上例omap beagle board中的omap_irq_chip结构体的赋值方式。

irq_action结构:

/**
 * struct irqaction - per interrupt action descriptor
 * @handler:	interrupt handler function
 * @flags:	flags (see IRQF_* above)
 * @name:	name of the device
 * @dev_id:	cookie to identify the device
 * @next:	pointer to the next irqaction for shared interrupts
 * @irq:	interrupt number
 * @dir:	pointer to the proc/irq/NN/name entry
 * @thread_fn:	interupt handler function for threaded interrupts
 * @thread:	thread pointer for threaded interrupts
 * @thread_flags:	flags related to @thread
 */
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;
};

irq_action就是中断的上半部分,对应具体的中断操作。

 

中断的函数调用流程图:

vector_irq--------> __irq_usr------->asm_do_IRQ---------->irq_desc[i]->handle_irq---------->handle_IRQ_envent---------->irq_desc[i]->action->handler

整个流程中最后调用到struct irqaction结构,当需要对中断mask时候,调用irq_chip结构。

 

linux中断之中断向量表:

linux中断向量表在entry_armv.s中。

	.globl	__vectors_start
__vectors_start:
 ARM(	swi	SYS_ERROR0	)
 THUMB(	svc	#0		)
 THUMB(	nop			)
	W(b)	vector_und + stubs_offset
	W(ldr)	pc, .LCvswi + stubs_offset
	W(b)	vector_pabt + stubs_offset
	W(b)	vector_dabt + stubs_offset
	W(b)	vector_addrexcptn + stubs_offset
	W(b)	vector_irq + stubs_offset
	W(b)	vector_fiq + stubs_offset

	.globl	__vectors_end
__vectors_end:

	.data

	.globl	cr_alignment
	.globl	cr_no_alignment
cr_alignment:
	.space	4
cr_no_alignment:
	.space	4

其实只是现实了跳转。网上有很多讨论这个原理的文章,可以google一把,有个张俊玲的  < arm linux 中断机制分析> pdf文档写的不错。当出现中断后,会vector_fiq + stubs_offset 跳转到__irq_usr处。

 

irq_usr进行 公用寄存器的 入栈等保护操作,然后调用irq_handler, irq_handler最后调用asm_do_IRQ。 asm_do_IRQ是C语言函数,有个知识点: 汇编调用C语言时,r0,r1,r2,作为c语言函数的三个参数,且最多就是这三个。 当从c语言函数进入汇编时候,c语言函数传入的参数也是放在 r0,r1,r2中。

 

PS: 在以前的arm中,中断向量表总是存放在0x0的位置。后来又可以放到0x0或者0xffff0000地方。现在的omap(其他芯片不清楚)已经可以放到任意位置了。有帖子讨论:http://e2e.ti.com/support/dsp/omap_applications_processors/f/447/t/29274.aspx

备份一下:

Hi,

I am currently trying to set up Vector table for a RTOS.

When i tried to write to 0X00000000 it caused prefetch abort exception.

 

Is it possible to write to 0xFFFF0000? Does this contain a valid memory for OMAP 3530.

Hence Can i map the Vectors to 0xFFFF0000 for OMAP3530 ? MMU is disabled in my case.

 

Thanks

Jack

 

I'm usually used to the MMU enabled and virtual FFFF0000 mapped to some location in SDRAM.  What you might want to try is to locate your execption vectors to SRAM.  There is a ROM in OMAP35x that starts at location 0x00000000 and ends somewhere else.  Looking at the TRM, when the MMU is disabled, the exception vectors in ROM will jump to vectors in SRAM.

Undefined is at 0x4020FFC8

SWI is at 0x4020FFCC

Prefetch Abort is at 0x4020FFD0

Data Abort is at 0x4020FFD4

Unused 0x4020FFD8

IRQ 0x4020FFDC

FIQ 0x4020FFE0

 

Thanks for you support.

In Cortex A8 , there is a provision for reloacating the vector to an address of your choice.

I have worked in ARM 9 and this feature was not there in ARM 9. Now i understand we can relocate to any address of our choice by writing into c 12 register (Vector Base register).

Now the Problem is solved..

 

 

Hi Jack, Greetings!!

I am trying to relocate the Vector Table on the OMAP 3530 from Address 0x00000000 (GPMC Area) to 0x40200000 (Internal SRAM). The MMU is Enabled.

I would like some input on writing into c 12 register (Vector Base register) as mentioned in your above post. Please provide some input.

Regards, Abel.

 

Hi Abel,

You can relocate the Vector base to any address of your choice.  

 ; set Vector base address to be 0x8000:0000
    mov    r0,  #0x80000000
    mcr    p15, #0, r0, c12, c0, #0

 in the above case the vector table is relocated to 0x80000000. 

 in your case it would be 0x40200000.

Thanks

Jack

 

Thanks Jack,

I was able to relocate the Vector table to the Internal SRAM.

Regards,

Abel

When i follow the TRM for the OMPA35xx and would like to overwrite the "dead loop handler" for a software interrupt, i have to do the following (my code is located in SDRAM starting from address 0x8000 0000).

Example:

// RAM exception vectors
 // In 0x4020FFCC is the Assembler Instruction: LDR PC,0x4020 FFE8
 *(unsigned int volatile *)0x4020FFCC = 0xE59FF014;
 // In 0x4020FFE8 is the address of my Software Interrupt Handler (in this example at the address 0x8000 0b50)
 *(unsigned int volatile *)0x4020FFE8 = (unsigned int)&MY_SWI_HANDLER;

Let's assume i generate a software exception, the callstack looks like this:

  1. Software interrupt generated
    0x80000C78:   EF000002 SWI             #2
  2. It jumps to the ROM SWI exception handler at address 0x14008 where the PC is set to the address stored in 0x14028
    0x00014008:   E59FF018 LDR             PC, 0x14028
  3. Context of 0x14028
    0x00014028:   4020FFCC
  4. Now the PC points to 0x4020FFCC, which i did modify in my code
    0x4020FFCC:   E59FF014 LDR             PC, 0x4020FFE8
  5. This will set the PC to address stored in 0x4020FFE8, which is my software interrupt handler
    0x4020FFE8:   80000B50
  6. Finally, the code reaches my software interrupt handler

So far so good. The big difference to the TRM is, i had to fill the 0x4020FFCC register manually.
Table 25-10 in the SPRUF98K claims, that 0x4020FFCC contains the following, but it's wrong:
 

Questions:

  1. This program flow generates two additional instructions before my ISR is invoked, isn't there a more elegant way to define the interrupt vector table?
  2. I tried to compile the assembler example above (to relocate the vector base) with Code Composer v4.2.1 TMS470, but failed with the following error:

    ERROR!   at line 4: [E0004] Immediate cannot be greater than 16-bits
    MOV r0, #0x4020FFCC

    Assembler code:
     .global _relocateVectorBaseRAM
    _relocateVectorBaseRAM:
       
    ; set Vector base to the RAM exception table
        MOV  r0, #0x4020FFCC
        MCR  p15, #0, r0, c12, c0, #0
        BX lr 
  3. What's the correct adress to relocate the exception vector for the OMAP35? Is 0x4020FFCC correct?
  4. Since the ARM Assembly Syntax and the TI Assembly Syntax are different (see comment), where can i find the syntax for the TI Assembly Language
  1. ERROR!   at line 4: [E0004] Immediate cannot be greater than 16-bits
    MOV r0, #0x4020FFCC

    Assembler code:
     .global _relocateVectorBaseRAM
    _relocateVectorBaseRAM:
       
    ; set Vector base to the RAM exception table
        MOV  r0, #0x4020FFCC
        MCR  p15, #0, r0, c12, c0, #0
        BX lr 
  2. What's the correct adress to relocate the exception vector for the OMAP35? Is 0x4020FFCC correct?

u cannot give "MOV  r0, #0x4020FFCC" as single instr as it will be greater than 32 bits.

ARM has fixed instruction size. max size is 32 bits.

You need to  use barrel shifter to input a 32bit value into the r0.

for eg:

    mov r5, #0x40
    mov r0, r5, lsl#24   ; we left *** r5 by 24 bits, then save that value into r0
    mov r5, #0x20        ; r5 = 0x20
    mov r5, r5, lsl#16   ; we left shift r5 by 15bits and save the value into r5 itself
    orr r0, r0, r5             ; logical OR r0 and r5 and save the result in r0.

r0 will now contain 0x40200000

 

 

linx中断之中断上半部处理分析:

asm_do_IRQ函数

/*
 * do_IRQ handles all hardware IRQ's.  Decoded IRQs should not
 * come via this function.  Instead, they should provide their
 * own 'handler'
 */
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);

	irq_enter();   //设置一些中断的变量等,如 preempt_count++;

	/*
	 * Some hardware gives randomly wrong interrupts.  Rather
	 * than crashing, do something sensible.
	 */
	if (unlikely(irq >= NR_IRQS)) {
		if (printk_ratelimit())
			printk(KERN_WARNING "Bad IRQ%u\n", irq);
		ack_bad_irq(irq);
	} else {
		generic_handle_irq(irq);        //中断处理
	}


	/* AT91 specific workaround */
	irq_finish(irq);

	irq_exit();                             //中断退出,设置退出标志。另外,这个函数会调用 软中断。
	set_irq_regs(old_regs);			//恢复reg
}

 

generic_handle_irq函数

/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt. If the descriptor is attached to an
 * irqchip-style controller then we call the ->handle_irq() handler,
 * and it calls __do_IRQ() if it's attached to an irqtype-style controller.
 */
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
	desc->handle_irq(irq, desc);
#else
	if (likely(desc->handle_irq))
		desc->handle_irq(irq, desc);         //调用
	else
		__do_IRQ(irq);
#endif
}

调用desc->handle_irq函数。

void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
	struct irqaction *action;
	irqreturn_t action_ret;

	raw_spin_lock(&desc->lock);
	mask_ack_irq(desc, irq);

	if (unlikely(desc->status & IRQ_INPROGRESS))
		goto out_unlock;
	desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
	kstat_incr_irqs_this_cpu(irq, desc);

	/*
	 * If its disabled or no action available
	 * keep it masked and get out of here
	 */
	action = desc->action;
	if (unlikely(!action || (desc->status & IRQ_DISABLED)))
		goto out_unlock;

	desc->status |= IRQ_INPROGRESS;
	raw_spin_unlock(&desc->lock);

	action_ret = handle_IRQ_event(irq, action);
	if (!noirqdebug)
		note_interrupt(irq, desc, action_ret);

	raw_spin_lock(&desc->lock);
	desc->status &= ~IRQ_INPROGRESS;

	if (unlikely(desc->status & IRQ_ONESHOT))
		desc->status |= IRQ_MASKED;
	else if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
		desc->chip->unmask(irq);
out_unlock:
	raw_spin_unlock(&desc->lock);
}

这个函数调用handle_IRQ_event函数,来调用action。 最后实现action的调用。另外:由于 有中断共享现象,所以可能会有好几个中断共享一个中断,则action指针指向一个list,list中存放所有的中断 处理函数。当发生中断后,会循环调用所有的这个list中的处理函数。

linx中断之中断下半部处理分析:

linux中断下半部分即是软中断,这里以http://bbs.chinaunix.net/thread-2333484-1-1.html文章(作者 九贱)做参考。

 

open_softirq向内核注册一个软中断,其实质是设置软中断向量表相应槽位,注册其处理函数:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}


每一个软中断对应一个softirq_action的值。

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

/* softirq mask and active fields moved to irq_cpustat_t in  * asm/hardirq.h to get better cache usage.  KAO  */

struct softirq_action {  void (*action)(struct softirq_action *); };

 

所有的软中断在linux中 有个位图来表示。当这个软中断 enable的时候,将对应的于该中断的相应bit置1.当disable的时候,则清0。

操作软中断的响应的操作:

 

#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)

置1相应的software irq。

 

#define local_softirq_pending() \
	__IRQ_STAT(smp_processor_id(), __softirq_pending)

读出整个位图。

raise_softirq:

void raise_softirq(unsigned int nr)
{
	unsigned long flags;

	local_irq_save(flags);     //先disable中断
	raise_softirq_irqoff(nr);  //主要这个函数来激活软中断
	local_irq_restore(flags);  //restore interrupt
}

raise_softirq函数用来enable and wake up software interrupt。

raise_softirq_irqoff:

/*
 * This function must run with irqs disabled!   //这就是为什么在调用该函数的时候要disable interrupt
 */        
inline void raise_softirq_irqoff(unsigned int nr)
{
	__raise_softirq_irqoff(nr);

	/*
	 * If we're in an interrupt or softirq, we're done
	 * (this also catches softirq-disabled code). We will
	 * actually run the softirq once we return from
	 * the irq or softirq.
	 *
	 * Otherwise we wake up ksoftirqd to make sure we
	 * schedule the softirq soon.
	 *///检测现在是否在中断中,如果在的话就返回。如果不在则唤醒ksoftirqd,ksoftirqd是一个内核线程。 他会调用do_softirq函数来执行软中断。
	if (!in_interrupt())  //in_interrupt函数会调用preempt_count()这个函数,他会检测记录中断状态的字段,来确认是否是中断中。
		wakeup_softirqd();
}


raise_softirq_irqoff什么时候被调用呢?

A、当do_IRQ完成了I/O中断时调用irq_exit:

#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq()        __do_softirq()
#else
# define invoke_softirq()        do_softirq()
#endif

void irq_exit(void)
{
        account_system_vtime(current);
        trace_hardirq_exit();
        sub_preempt_count(IRQ_EXIT_OFFSET);
        if (!in_interrupt() && local_softirq_pending())
                invoke_softirq();                //调用软中断

B、如果系统使用I/O APIC,在处理完本地时钟中断时:

void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)
{
        ……
        irq_exit();
        ……
}

C、local_bh_enable

local_bh_enable就是打开下半部,当然重中之中就是软中断了:

void local_bh_enable(void)
{
        _local_bh_enable_ip((unsigned long)__builtin_return_address(0));
}

static inline void _local_bh_enable_ip(unsigned long ip)
{
        ……

        if (unlikely(!in_interrupt() && local_softirq_pending()))
                do_softirq();

        ……
}

D、在SMP中,当CPU处理完被CALL_FUNCTION_VECTOR处理器间中断所触发的函数时:
唔,对多核中CPU的之间的通信不熟,不太清楚这个机制…… .

 

do_irq函数就不分析,没什么鸟用。

 

tasklet机制

另外:在linux系统中,其实用 software riq的中断是很少的,这里只是分析了software irq,很多下半部用的是tasklet机制,tasklet是一个基于软中断上的机制,他将软中断封装,使更加容易使用等。

tasklet机制可以参照http://blog.csdn.net/sailor_8318/archive/2008/07/13/2645186.aspx学习。


阅读更多
个人分类: linux内核源码分析
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭