您的位置:首页 > 其它

Davinci DM6446 的ARM中断处理流程(DM644X)

2011-12-05 14:31 344 查看
源地址:http://bbs.ivsok.com/blog-2-6.html

本文分析了DM644x平台arm926ejs中断的流程。

1. arm硬件中断向量表建立及中断响应都在linux/arch/arm/kernel/entry-armv.S中,故从该文件

开始分析。

linux/arch/arm/kernel/entry-armv.S:

__stubs_end:

.equ __real_stubs_start, .LCvectors + 0x200

.LCvectors:

swi SYS_ERROR0

b __real_stubs_start + (vector_und - __stubs_start)

ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)

b __real_stubs_start + (vector_pabt - __stubs_start)

b __real_stubs_start + (vector_dabt - __stubs_start)

b __real_stubs_start + (vector_addrexcptn - __stubs_start)

b __real_stubs_start + (vector_irq - __stubs_start)

b __real_stubs_start + (vector_fiq - __stubs_start)

ENTRY(__trap_init)

stmfd {r4 - r6, lr}

mov r0, #0xff000000

orr r0, r0, #0x00ff0000 @ high vectors position

adr r1, .LCvectors @ set up the vectors

ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}

stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}

add r2, r0, #0x200

adr r1, __stubs_start @ copy stubs to 0x200

adr r4, __stubs_end

1: ldr r3, [r1], #4

str r3, [r2], #4

cmp r1, r4

blo 1b

add r2, r0, #0x1000 @ top of high vector page

adr r4, __kuser_helper_end @ user helpers to top of page

adr r1, __kuser_helper_start @ going downwards.

1: ldr r3, [r4, #-4]!

str r3, [r2, #-4]!

cmp r4, r1

bhi 1b

LOADREGS(fd, {r4 - r6, pc})

内核解压启动完成一些初始化后,执行init/main.c中的start_kernel()函数,然后到过程便是,

start_kernel()-->setup_arch()-->early_trap_init()-->__trap_init()。

__trap_init()例程首先將7个异常向量从.LCvectors处拷贝到0xffff0000(arm的高向量地址),

然后再把__stubs_start到__stubs_end之间到代码也就是具体的异常处理程序拷贝到相对0xffff0000

偏移0x200处.

现在假设在usr mode发生一个irq,arm硬件将保存当前的pc到lr_irq和当前状态寄存器cpsr到

spsr_irq,经0xffff0018处的中断异常向量跳转至vector_IRQ处的处理程序:

.macro vector_stub, name, sym, correction=0

.align 5

vector_\name:

ldr r13, .LCs\sym

.if \correction

sub lr, lr, #\correction

.endif

str lr, [r13] @ save lr_IRQ

mrs lr, spsr

str lr, [r13, #4] @ save spsr_IRQ

@

@ now branch to the relevant MODE handling routine

@

mrs r13, cpsr

bic r13, r13, #MODE_MASK

orr r13, r13, #MODE_SVC

msr spsr_cxsf, r13 @ switch to SVC_32 mode

/*

lr&15的值是表明发生irq之前cpu所在的空间,

0:在用户空间发生了irq中断

3:在内核空间发生了irq中断

lr<<2=lr*4也就是pc+0和pc+12

分别对应LCtab_irq的.word __irq_usr域和.word __irq_svc域

*/

and lr, lr, #15

ldr lr, [pc, lr, lsl #2]

movs pc, lr @ Changes mode and branches

.endm

__stubs_start:

/*

* Interrupt dispatcher

*/

vector_stub irq, irq, 4 // 这就是vector_irq

.long __irq_usr @ 0 (USR_26 / USR_32)

.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)

.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)

.long __irq_svc @ 3 (SVC_26 / SVC_32)

.long __irq_invalid @ 4

.long __irq_invalid @ 5

.long __irq_invalid @ 6

.long __irq_invalid @ 7

.long __irq_invalid @ 8

.long __irq_invalid @ 9

.long __irq_invalid @ a

.long __irq_invalid @ b

.long __irq_invalid @ c

.long __irq_invalid @ d

.long __irq_invalid @ e

.long __irq_invalid @ f

这段程序在前面已经被拷贝到0xffff0000+0x200后面的地方.这段代码运行时cpu为irq mode,

最后movs pc, lr一句根据cpu被中断时到运行模式跳转入__irq_usr的同时cpu还跳转到到svc mode模式,

所以先临时保存影子寄存器lr_IRQ和spsr_IRQ,也就是pc_usr和cpsr_usr。

从上面的程序可以看出,除了usr和svc mode外,在其他运行模式下发生的irq无效,即不处理.

__irq_usr:

sub sp, sp, #S_FRAME_SIZE

stmia sp, {r0 - r12} @ save r0 - r12

ldr r4, .LCirq

add r8, sp, #S_PC

ldmia r4, {r5 - r7} @ get saved PC, SPSR

#if __LINUX_ARM_ARCH__ < 6

@ make sure our user space atomic helper is aborted

cmp r5, #VIRT_OFFSET

bichs r6, r6, #PSR_Z_BIT

#endif

stmia r8, {r5 - r7} @ save pc, psr, old_r0

stmdb r8, {sp, lr}^

alignment_trap r4, r7, __temp_irq

zero_fp

#ifdef CONFIG_PREEMPT

get_thread_info r8

ldr r9, [r8, #TI_PREEMPT] @ get
preempt count

// 增加抢占计数器的值,preempt_count为正数时,禁止抢占

add r7, r9, #1 @ increment it

str r7, [r8, #TI_PREEMPT]

#endif

1: get_irqnr_and_base r0, r6, r5, lr

movne r1, sp

adrsvc ne, lr, 1b

@

@ routine called with r0 = irq number, r1 = struct pt_regs *

@

bne asm_do_IRQ

#ifdef CONFIG_PREEMPT

ldr r0, [r8, #TI_PREEMPT]

teq r0, r7

str r9, [r8, #TI_PREEMPT] //
恢复原值

strne r0, [r0, -r0]

mov tsk, r8

#else

get_thread_info tsk

#endif

mov why, #0

b ret_to_user

dm644x arm cpu中的硬件中断靠中断请求寄存器的位来区分,但由于其有跳转表寄存器(IRQENTRY),

故通过(IRQENTRY/4) - 1获得中断号,get_irqnr_and_base宏代码就是实现这个功能。中断号保存到

r0,sp保存到r1,r0与r1随后作为参数传送给asm_do_IRQ()例程,其中sp已经被压入了中断上现场的各个

寄存器值,也就是pt_reg结构:

.macro get_irqnr_and_base, irqnr, irqstat, base, tmp

/* GIVEN:

* EABASE = 0 ... so IRQNR = (IRQENTRY/4) - 1

* RETURN:

* irqnr: Interrupt number. Zero corresponds

* to bit 0 of the status register

* irqstat, base, and tmp may be considered

* as scratch registers

* Z conditions means no outstanding interrupt

*/

ldr \base, =IO_ADDRESS(DAVINCI_ARM_INTC_BASE)

ldr \tmp, [\base, #0x14]

mov \tmp, \tmp, lsr #2

sub \irqnr, \tmp, #1

cmp \tmp, #0 // 用于判断是否真的有中断发生

.endm

struct pt_regs {

long uregs[17];

};

#define ARM_pc uregs[15]

#define ARM_lr uregs[14]

#define ARM_sp uregs[13]

#define ARM_ip uregs[12]

#define ARM_fp uregs[11]

#define ARM_r10 uregs[10]

#define ARM_r9 uregs[9]

#define ARM_r8 uregs[8]

#define ARM_r7 uregs[7]

#define ARM_r6 uregs[6]

#define ARM_r5 uregs[5]

#define ARM_r4 uregs[4]

#define ARM_r3 uregs[3]

#define ARM_r2 uregs[2]

#define ARM_r1 uregs[1]

#define ARM_r0 uregs[0]

#define ARM_ORIG_r0 uregs[16] -----除了SWI调用,其值一直是-1。

值得注意一下的是adrsvc ne, lr, 1b一句,通过设置asm_do_IRQ()的返回地址lr,实现了对中断的

串行处理。

2.进入asm_do_IRQ()例程。

linux/arch/arm/kernel/irq.c:

asmlinkage notrace void

asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

{

struct irqdesc *desc = irq_desc + irq;

/*

如果打开了追踪内核事件的开关,则

记录硬中断事件,!(user_mode(regs))用于判断是否在内核中。

*/

ltt_ev_irq_entry(irq, !(user_mode(regs)));

/*

* Some hardware gives randomly wrong interrupts. Rather

* than crashing, do something sensible.

*/

if (irq >= NR_IRQS)

desc = &bad_irq_desc;

interrupt_overhead_start();

// 增加抢占计数器的值

irq_enter();

spin_lock(&irq_controller_lock);

/*

该handle在arch/arm/mach-davinci/irq.c中注册,在dm644x平台中为do_edge_IRQ()或

do_level_IRQ(),这两个例程中会调用用户注册的中断例程。

*/

desc->handle(irq, desc, regs);

/*

* Now re-run any pending interrupts.

*/

// 检查是否有的等待处理到中断请求

if (!list_empty(&irq_pending))

do_pending_irqs(regs); // 处理延后的中断请求

spin_unlock(&irq_controller_lock);

// 退出中断上下文并检查是否有软中断发生

irq_exit();

/*

检查延迟中断并记录

*/

latency_check();

// 记录irq进入事件,其实在该平台什么也没做。

ltt_ev_irq_exit();

}

在上面的asm_do_IRQ例程中,调用的desc->handle(irq, desc, regs)在dm644x平台中有两种类型,

do_edge_IRQ()和do_level_IRQ()。下面的分析都是基于asm_do_IRQ例程中的代码。

3.进入desc->handle(),这里假设指向do_edge_IRQ()例程:

linux/arch/arm/kernel/irq.c:

/*

* Most edge-triggered IRQ implementations seem to take a broken

* approach to this. Hence the complexity.

*/

void

do_edge_IRQ(unsigned int irq, struct irqdesc *desc, struct pt_regs *regs)

{

const unsigned int cpu = smp_processor_id();

desc->triggered = 1;

/*

* If we're currently running this IRQ, or its disabled,

* we shouldn't process the IRQ. Instead, turn on the

* hardware masks.

*/

/*

desc->disable_depth为正数时,表明中断被禁止,操作该变量的例程是disable_irq()和

enable_irq(),前者增加disable_depth的值,后者减少disable_depth的值。

*/

if (unlikely(desc->running || desc->disable_depth))

goto running;

/*

* Acknowledge and clear the IRQ, but don't mask it.

*/

// 该ack()在arch/arm/mach-davinci/irq.c中注册,用来清除中断请求寄存器的相应位。

desc->chip->ack(irq);

/*

* Mark the IRQ currently in progress.

*/

desc->running = 1; // 中断正在被处理

kstat_cpu(cpu).irqs[irq]++; //统计irq线发生中断的次数

#ifdef CONFIG_PREEMPT_HARDIRQS

if (desc->action) desc->status |= IRQ_INPROGRESS;

if (redirect_hardirq(desc))

return;

#endif

do {

struct irqaction *action;

action = desc->action;

if (!action) // 检查用户是否注册了中断例程

break;

/*

如果允许打开中断,则调用unmask()例程打开中断(向中断使能寄存器写1),

unmask()例程在arch/arm/mach-davinci/irq.c中注册。

*/

if (desc->pending && !desc->disable_depth) {

desc->pending = 0;

desc->chip->unmask(irq);

}

// 执行用户注册的中断例程,其包含在action结构体的handler中

__do_irq(irq, action, regs);

} while (desc->pending && !desc->disable_depth);//如果又有中断发生,继续执行

desc->status &= ~IRQ_INPROGRESS;

desc->running = 0;

/*

* If we were disabled or freed, shut down the handler.

*/

if (likely(desc->action && !check_irq_lock(desc, irq, regs)))

return;

running:

/*

* We got another IRQ while this one was masked or

* currently running. Delay it.

*/

desc->pending = 1;

desc->status |= IRQ_PENDING;

desc->chip->mask(irq);

desc->chip->ack(irq);

}

4.进入__do_irq例程,其运行用户注册的中断例程.

linux/arch/arm/kernel/irq.c:

static int

__do_irq(unsigned int irq, struct irqaction *action, struct pt_regs *regs)

{

unsigned int status;

int ret, retval = 0;

spin_unlock(&irq_controller_lock);

// 如果硬中断没有嵌套且该中断不是快速中断的话则打开中断,以允许中断嵌套

if (!hardirq_count() || !(action->flags & SA_INTERRUPT))

local_irq_enable();

interrupt_overhead_stop();

status = 0;

// 执行用户注册到中断例程,一个中断线可以被共享,也就是可以注册多个用户中断例程

do {

ret = action->handler(irq, action->dev_id, regs);

if (ret == IRQ_HANDLED)

status |= action->flags;

retval |= ret;

action = action->next;

} while (action);

if (status & SA_SAMPLE_RANDOM)

add_interrupt_randomness(irq);

#ifdef CONFIG_NO_IDLE_HZ

if (timer_update.function && irq != timer_update.skip)

timer_update.function(irq, 0, regs);

#endif

spin_lock_irq(&irq_controller_lock);

return retval;

}

5.进入do_pending_irqs()例程,处理延后的中断请求。

linux/arch/arm/kernel/irq.c:

static void do_pending_irqs(struct pt_regs *regs)

{

struct list_head head, *l, *n;

do {

struct irqdesc *desc;

/*

* First, take the pending interrupts off the list.

* The act of calling the handlers may add some IRQs

* back onto the list.

*/

head = irq_pending; // 获取延后中断列表

INIT_LIST_HEAD(&irq_pending); // 清空延后中断列表

head.next->prev = &head;

head.prev->next = &head;

/*

* Now run each entry. We must delete it from our

* list before calling the handler.

*/

// 获取所有延后的中断描述符结构体指针,并调用中断处理例程desc->handle()

list_for_each_safe(l, n, &head) {

desc = list_entry(l, struct irqdesc, pend);

list_del_init(&desc->pend);

desc->handle(desc - irq_desc, desc, regs);

}

/*

* The list must be empty.

*/

BUG_ON(!list_empty(&head));

} while (!list_empty(&irq_pending));//
该例程在运行的时候可能又产生了新的延后中断

}

6.进入irq_exit()例程,该例程会检查是否有软中断发生。

linux/arch/arm/kernel/irq.c:

// kernel/softirq.c

void irq_exit(void)

{

// 减少硬中断计数器的值

sub_preempt_count(IRQ_EXIT_OFFSET);

// 判断是否在硬中断或软中断上下文中,是否有软中断发生

if (!in_interrupt() && local_softirq_pending())

invoke_softirq(); // 处理软中断

// 减少抢占计数器的值

preempt_enable_no_resched();

}

因为软中断是穿串行处理的,所以不允许发生在软中断上下文中。

7.进入软中断处理例程__do_softirq(),其其实就是invoke_softirq()的#define定义:

linux/kernel/softirq.c:

# define invoke_softirq() __do_softirq()

/*

* We restart softirq processing MAX_SOFTIRQ_RESTART times,

* and we fall back to softirqd after that.

*

* This number has been established via experimentation.

* The two things to balance is latency against fairness -

* we want to handle softirqs as soon as possible, but they

* should not be able to lock up the box.

*/

#define MAX_SOFTIRQ_RESTART 10

asmlinkage void ___do_softirq(void)

{

struct softirq_action *h;

__u32 pending;

int max_restart = MAX_SOFTIRQ_RESTART;

int cpu;

// 把本地cpu软中断的位掩码复制到局部变量pending中

pending = local_softirq_pending();

cpu = smp_processor_id();

restart:

/* Reset the pending bitmask before enabling irqs */

local_softirq_pending() = 0;

local_irq_enable(); // 开中断,允许硬中断

h = softirq_vec; // 获取软中断结构体数组

do {

if (pending & 1) { //
数组下标越小,软中断优先级越高

{

u32 preempt_count = preempt_count();

// 如果打开了追踪内核事件的开关,记录软中断事件

ltt_ev_soft_irq(LTT_EV_SOFT_IRQ_SOFT_IRQ, (h - softirq_vec));

// 执行注册的软中断

h->action(h);

if (preempt_count != preempt_count()) {

print_symbol("softirq preempt bug: exited %s with wrong

preemption count!\n", (unsigned long) h->action);

printk("entered with %08x, exited with %08x.\n",

preempt_count, preempt_count());

preempt_count() = preempt_count;

}

}

rcu_bh_qsctr_inc(cpu);

cond_resched_all(); // 检查是否需要调度程序

}

h++;

pending >>= 1;

} while (pending);

local_irq_disable();

pending = local_softirq_pending();

/*

如果pending不为0,且循环没有达到指定到10次,则继续执行软中断

*/

if (pending && --max_restart)

goto restart;

if (pending) // 如果循环超过了10次,则唤醒内核线程wakeup_softirqd()延后执行

wakeup_softirqd();

}

从asm_do_IRQ()中返回后再用get_irqnr_and_base宏检查是否又有新的中断发生(这就是前面讲到串行

处理),否則在get_thread_info tsk宏中通过sp得到task_struct的地址,并跳到

ret_to_user:

get_thread_info tsk

mov why, #0

b ret_to_user

从以上能看出,linux系统的中断处理过程比较完善,对于一个操作系统来说是比较适合的。但是就是因为

完善,所以整个中断过程比较长,耗费的时间比裸跑的中断多很多,正所谓有利有弊,各取所需。

本文转自互联网
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: