arm_linux中断

来源:互联网 发布:java 项目时区设置 编辑:程序博客网 时间:2024/06/05 18:28
 

一,认识几个重要结构体:

1.中断描述符

对于每一条中断线都由一个irq_desc结构来描述。

//include/linux/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

/*

kernel/irq/chip.c中实现了5个函数:handle_simple_irq(),handle_level_irq(),
 handle_edge_irq(),handle_fasteoi_irq()以及handle_percpu_irq()。 handle_irq指针可
以指向这5个函数中的一个, 选择一种中断事件处理策略, 这是通过函数set_irq_handler()
完成的

*/
 irq_flow_handler_t handle_irq;//上层中断处理函数,
 struct irq_chip  *chip;//底层硬件操作
 struct msi_desc  *msi_desc;
 void   *handler_data;//附加参数,用于handle_irq
 void   *chip_data;//平台相关附加参数,用于chip
 struct irqaction *action; /* IRQ action list *///中断服务例程链表
 unsigned int  status;  /* IRQ status *///中断当前的状态

//中断关闭打开层数调用一次disable_irq( ) depth1;调用一次enable_irq( )该值减1

//如果depth等于0就开启这条中断线,如果depth大于0就关闭中断线。

 unsigned int  depth; 
 unsigned int  wake_depth; ////* 唤醒次数 */
 unsigned int  irq_count; /* 发生的中断次数 */
 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  cpu;
#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;///proc/irq/ 入口
#endif
 const char  *name;///proc/interrupts 中显示的中断名称
} ____cacheline_internodealigned_in_smp;


/*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),
 }
};

NR_IRQS为最大中断数对于s3c2410芯片,在文件arch/arm/mach-s3c2410/include/mach/irqs.h中定义如下:

#ifdef CONFIG_CPU_S3C2443
#define NR_IRQS (IRQ_S3C2443_AC97+1)
#else
#define NR_IRQS (IRQ_S3C2440_AC97+1)//每一个中断源都对应一个irq_desc结构体。
#endif

*/

2. 中断硬件操作函数集

//include/linux/irq.h中定义

//该结构体中各函数在文件linux/arch/arm/plat-s3c24xx/irq.c中实现

struct irq_chip {
 const char *name; //用于 /proc/interrupts
 unsigned int (*startup)(unsigned int irq); //默认为 enable 如果为NULL
 void  (*shutdown)(unsigned int irq); //默认为 disable 如果为NULL
 void  (*enable)(unsigned int irq); //允许中断,默认为 unmask 如果为NULL
 void  (*disable)(unsigned int irq); //禁止中断,默认为 mask 如果为 NULL

 void  (*ack)(unsigned int irq); //响应一个中断,清除中断标志
 void  (*mask)(unsigned int irq); //mask 一个中断源,通常是关闭中断
 void  (*mask_ack)(unsigned int irq); //响应并 mask 中断源
 void  (*unmask)(unsigned int irq); //unmask 中断源
 void  (*eoi)(unsigned int irq);

 void  (*end)(unsigned int irq);
 void  (*set_affinity)(unsigned int irq,
     const struct cpumask *dest);
 int  (*retrigger)(unsigned int irq);
 int  (*set_type)(unsigned int irq, unsigned int flow_type); //设置中断触发方式 IRQ_TYPE_LEVEL
 int  (*set_wake)(unsigned int irq, unsigned int on);

 /* 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;
};

3.中断处理例程描述符

//include/linux/interrupt.h

struct irqaction {
 irq_handler_t handler; /* 具体的中断处理程序 */
 unsigned long flags; //用一组标志描述中断线与 I/O 设备之间的关系。
 cpumask_t mask;
 const char *name; /* 名称,会显示在/proc/interreupts中 */
 void *dev_id; /* 设备ID,用于区分共享一条中断线的多个处理程序 ,以便从共享中断线的诸多中断处理程序中删除指定的那一个*/
 struct irqaction *next; /* 指向下一个irq_action结构 */
 int irq;  /* 中断通道号 */
 struct proc_dir_entry *dir;  /* procfs目录 */
 irq_handler_t thread_fn;
 struct task_struct *thread;
 unsigned long thread_flags;
};

 

这三个结构体间的关系表示如下

 

二,中断初始化过程

 

中断机制的初始化通过 两个函数完成:early_trap_init()init_IRQ(),在此我们先讨论函数init_IRQ()

//函数init_IRQ在文件linux/arch/arm/kernel/irq.c中实现。

void __init init_IRQ(void)
{
 int irq;

/*  设置 irq_desc 数组的 status 为 IRQ_NOREQUEST | IRQ_NOPROBE(没有请求,没有检测)  */

 for (irq = 0; irq < NR_IRQS; irq++)
  irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

#ifdef CONFIG_SMP
 cpumask_setall(bad_irq_desc.affinity);
 bad_irq_desc.cpu = smp_processor_id();
#endif

/*

init_arch_irq在文件linux/arch/arm/kernel/irq.c中定义如下

void (*init_arch_irq)(void) __initdata = NULL;

该函数指针 在 setup_arch()中被赋值,

init_arch_irq = mdesc->init_irq;

指向 machine_desc 中定义的 init_irq 函数。

在平台smdk2440中,该函数在文件linux/arch/arm/plat-s3c24xx/irq.c中实现。

*/
 init_arch_irq();
}

//函数s3c24xx_init_irq在文件linux/arch/arm/plat-s3c24xx/irq.c中实现

void __init s3c24xx_init_irq(void)
{
 unsigned long pend;
 unsigned long last;
 int irqno;
 int i;

 irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");

 /* first, clear all interrupts pending... */

 last = 0;
 for (i = 0; i < 4; i++) {
  pend = __raw_readl(S3C24XX_EINTPEND);

  if (pend == 0 || pend == last)
   break;

  __raw_writel(pend, S3C24XX_EINTPEND); //清除外部中断寄存器EINTPEND中的请求标志,
  printk("irq: clearing pending ext status %08x\n", (int)pend);
  last = pend;
 }

 last = 0;


。。。。。。

 

 //设置各中断的底层硬件操作函数集desc->chip,中断上层处理函数desc->handle_irq

 for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
  /* set all the s3c2410 internal irqs */

  switch (irqno) {
   /* deal with the special IRQs (cascaded) */

  case IRQ_EINT4t7:
  case IRQ_EINT8t23:
  case IRQ_UART0:
  case IRQ_UART1:
  case IRQ_UART2:
  case IRQ_ADCPARENT:
   set_irq_chip(irqno, &s3c_irq_level_chip);
   set_irq_handler(irqno, handle_level_irq);
   break;

  case IRQ_RESERVED6:
  case IRQ_RESERVED24:
   /* no IRQ here */
   break;

  default:
   //irqdbf("registering irq %d (s3c irq)\n", irqno);
   set_irq_chip(irqno, &s3c_irq_chip);
   set_irq_handler(irqno, handle_edge_irq);
   set_irq_flags(irqno, IRQF_VALID);
  }
 }

 //以下几个中断都有多个中断源,每一个中断源也都有各自的中断号,它们的多个中断源中任意一个产生中断

//该中断都会被触发,而不是直接出发子中断。这几个中断并不处理中断函数,它们的中作是计算子中断的中断号,

//并根据子中断的中断号在数组irq_desc[NR_IRQS]中去找出该中断号对应的irq_desc结构,并调用该结构中的中断处理函数。

 set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
 set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);

 set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
 set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
 set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
 set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

 

。。。。。。

 

 for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
  irqdbf("registering irq %d (extended s3c irq)\n", irqno);
  set_irq_chip(irqno, &s3c_irqext_chip);  //设置子中断的硬件操作函数集
  set_irq_handler(irqno, handle_edge_irq); //设置子中断的上层处理函数
  set_irq_flags(irqno, IRQF_VALID);
 }

 

。。。。。。

 
}

 比如此时外部中断10产生了中断,中断号为IRQ_EINT8t23的中断被触发,执行函数s3c_irq_demux_extint8()

static void
s3c_irq_demux_extint8(unsigned int irq,
        struct irq_desc *desc)
{
 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);

 eintpnd &= ~eintmsk;
 eintpnd &= ~0xff; /* ignore lower irqs */

 /* we may as well handle all the pending IRQs here */

 while (eintpnd) {
  irq = __ffs(eintpnd);  //计算该中断在外部中断寄存器EINTPEND中的偏移量
  eintpnd &= ~(1<<irq);

  irq += (IRQ_EINT4 - 4); //根据这个偏移量重新计算中断号
  generic_handle_irq(irq); //根据重新计算的中断号获取对应的结构体irq_desc,并调用它的上层中断处理函数。
 }

}

 

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); //通用中断处理函数,该函数最终调用desc->handle_irq(irq, desc);
#endif
}

 

上层中断处理函数

上层中断处理函数有5个分别为:handle_simple_irq(),handle_level_irq(),
 handle_edge_irq(),handle_fasteoi_irq()以及handle_percpu_irq()

这几个函数在文件kernel/irq/chip.c中实现。常用的有两个handle_level_irq(),handle_edge_irq()

5个上层中断处理函数都是通过调用函数handle_IRQ_event()来做进一步处理。

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
 irqreturn_t ret, retval = IRQ_NONE;
 unsigned int status = 0;

 if (!(action->flags & IRQF_DISABLED))
  local_irq_enable_in_hardirq();

 do {


。。。。。。


  ret = action->handler(irq, action->dev_id); //执行中断处理函数。


。。。。。。

 

  retval |= ret;
  action = action->next;
 } while (action); //调用该中断线上的所有例程

 if (status & IRQF_SAMPLE_RANDOM)
  add_interrupt_randomness(irq);
 local_irq_disable();

 return retval;
}

 

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

。。。。。。


 desc = irq_remap_to_desc(irq, desc);

。。。。。。


 action = desc->action;

 action_ret = handle_IRQ_event(irq, action);


。。。。。。


}

 

void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
 spin_lock(&desc->lock);

。。。。。。
  desc = irq_remap_to_desc(irq, desc);
。。。。。。
 desc->status |= IRQ_INPROGRESS;

 do {
  struct irqaction *action = desc->action;
 。。。。。。

 

  desc->status &= ~IRQ_PENDING;
  spin_unlock(&desc->lock);
  action_ret = handle_IRQ_event(irq, action);
  if (!noirqdebug)
   note_interrupt(irq, desc, action_ret);
  spin_lock(&desc->lock);

//该函数与函数handle_level_irq不太一样的是,该函数多了一个循环。即如果在本次中断

//的处理过程中该中断线上又有中断产生,则再次执行该中断线上的处理例程

/*

以下是5个常用的中断线状态。

#define IRQ_INPROGRESS 1 /* 正在执行这个 IRQ 的一个处理程序 */
#define IRQ_DISABLED 2 /* 由设备驱动程序已经禁用了这条 IRQ 中断线 */

#define IRQ_PENDING 4 /* 一个 IRQ 已经出现在中断线上,且被应答,但还没有
为它提供服务 */
#define IRQ_REPLAY 8 /* 当 Linux 重新发送一个已被删除的 IRQ 时 */
#define IRQ_WAITING 32 /* 当对硬件设备进行探测时,设置这个状态以标记正在被
测试的 irq */

*/

 } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

 desc->status &= ~IRQ_INPROGRESS;
out_unlock:
 spin_unlock(&desc->lock);
}

//现在来看看中断初始化的另一个函数early_trap_init(),该函数在文件arch/arm/kernel/traps.c中实现。

void __init early_trap_init(void)
{

//CONFIG_VECTORS_BASEautoconf.h中定义(该文件自动成生),值为0xffff0000,
 unsigned long vectors = CONFIG_VECTORS_BASE;
 extern char __stubs_start[], __stubs_end[];
 extern char __vectors_start[], __vectors_end[];
 extern char __kuser_helper_start[], __kuser_helper_end[];
 int kuser_sz = __kuser_helper_end - __kuser_helper_start

 

/*  异常向量表拷贝到 0x0000_0000(或 0xFFFF_0000) ,
 异常处理程序的 stub 拷贝到 0x0000_0200(或 0xFFFF_0200) */ 
 memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
 memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
 memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

 

/*  拷贝信号处理函数 */
 memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
        sizeof(sigreturn_codes));

/*  刷新 Cache,修改异常向量表占据的页面的访问权限*/

 flush_icache_range(vectors, vectors + PAGE_SIZE);
 modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

这个函数把定义在 arch/arm/kernel/entry-armv.S 中的异常向量表和异常处理程序的 stub 进行
重定位:异常向量表拷贝到 0xFFFF_0000,异常向量处理程序的 stub 拷贝到 0xFFFF_0200
然后调用 modify_domain()修改了异常向量表所占据的页面的访问权限,这使得用户态无法
访问该页,只有核心态才可以访问。
  
arm处理器发生异常时总会跳转到 0xFFFF_0000(设为高端向量配置时)处的异常向量
表,因此进行这个重定位工作。

异常向量表,在文件arch/arm/kernel/entry-armv.S 

 .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

 .globl __vectors_start
__vectors_start:
 swi SYS_ERROR0
 b vector_und + stubs_offset //复位异常:
 ldr pc, .LCvswi + stubs_offset //未定义指令异常:
 b vector_pabt + stubs_offset //软件中断异常:
 b vector_dabt + stubs_offset //数据异常:
 b vector_addrexcptn + stubs_offset  //保留:
 b vector_irq + stubs_offset  //普通中断异常:
 b vector_fiq + stubs_offset  //快速中断异常:

 .globl __vectors_end
__vectors_end:

当 ARM 处理器发生异常(中断是一种异常)时,会跳转到异常向量表,在向量表中找到相应的异常,并跳转到

该异常处理程序处执行。

stubs_offset,定义为__vectors_start + 0x200 - __stubs_start

在中断初始化函数early_trap_init()中向量表被拷到0xFFFF_0000处,异常处理程序段被拷到0xFFFF_0200处。

比如此时发生中断异常b vector_irq + stubs_offset  将跳转到中断异常处理程序段去执行,由于vector_irq

在异常处理程序段__stubs_start__stubs_end之间此时跳转的位置将是__vectors_start + 0x200 + vector_irq - __stubs_start处。

 

异常处理程序段如下:

当 ARM 处理器发生异常(中断是一种异常)时,会跳转到异常向量表,在向量表中找到相应的异常,并跳转到

该异常处理程序处执行,这些异常处理程序即是放在以下异常处理程序段中。

 .globl __stubs_start
__stubs_start:

//vector_stub是一个宏,它代表有一段程序放在此处。irq, IRQ_MODE, 4是传递给宏vector_stub的参数。
 vector_stub irq, IRQ_MODE, 4

//以下是跳转表,在宏vector_stub代表的程序段中要用到该表来查找程序要跳转的位置。

//如果在进入终中断时是用户模式,则调用__irq_usr例程,如果为系统模式,则调用__irq_svc,如果是其他模式,则说明出错了,

//则调用__irq_invalid

 .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


 vector_stub dabt, ABT_MODE, 8

 .。。。。。。


 vector_stub pabt, ABT_MODE, 4

。。。。。。


 vector_stub und, UND_MODE

。。。。。。


vector_fiq:
 disable_fiq
 subs pc, lr, #4

vector_addrexcptn:
 b vector_addrexcptn
 .align 5

.LCvswi:
 .word vector_swi

 .globl __stubs_end
__stubs_end:

vector_stub代表的程序段如下:name, mode, correction存储传入的参数之

 .macro vector_stub, name, mode, correction=0
 .align 5

vector_\name:
 .if \correction
 sub lr, lr, #\correction //修正返回地址,也就是中断处理完之后要执行的指令的地址
 .endif

 @
 @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
 @ (parent CPSR)
 @

///保存返回地址到堆栈,因为很快要使用r0寄存器,所以也要保存r0sp后没有!所以sp指向的位置并没有变化。

 stmia sp, {r0, lr}  @ save r0, lr  

 mrs lr, spsr 
 str lr, [sp, #8]  @ save spsr

// 向上增长的栈。

// 此时的这个栈是中断模式下的栈,ARM下中断模式下和系统模式下的

// 栈是不同的。虽然ARM提供了七个模式,但Linux只使用了两个,一

// 个是用户模式,另一个为系统模式,所以这个栈只是一个临时性的栈。

/*

arch/arm/include/asm/ptrace.h中有处理器的七种工作模式的定义

#define USR_MODE 0x00000010
#define FIQ_MODE 0x00000011
#define IRQ_MODE 0x00000012
#define SVC_MODE 0x00000013
#define ABT_MODE 0x00000017
#define UND_MODE 0x0000001b
#define SYSTEM_MODE 0x0000001f

*/
 mrs r0, cpsr
 eor r0, r0, #(\mode ^ SVC_MODE)
 msr spsr_cxsf, r0 ////spsr设置为管理模式。//spsr的所有控制为进行写操作,r0的值全部注入spsr

 @
 @ the branch table must immediately follow this code
 @
 //and lr, lr, #0x0f //  这条指令之后lr中位spsr的低4位,上面跳转表有16项就是对应这16个状态
 //mov r0, sp //r0保存堆栈指针的地址

//在对这段程序分析时要记住这段程序是以宏vector_stub的形式放在跳转表前面的。

//将跳转表中对应的地址条目存入lr。因为跳转表中每一个条目都是4个字节long,所以此处左移两位
 ldr lr, [pc, lr, lsl #2]

  movs pc, lr   @ branch to handler in SVC mode//程序跳转。
ENDPROC(vector_\name)
 .endm

 

在此我们以在用户空间发生中断异常为例,即程序跳转到__irq_usr处。

 .align 5
__irq_usr:
 usr_entry   //usr_entry是一个宏代表一段程序插入此处,宏usr_entry所代表的程序段将在下面分析        (1)

 kuser_cmpxchg_check

#ifdef CONFIG_TRACE_IRQFLAGS
 bl trace_hardirqs_off
#endif

//接着看get_thread_info, 它也是个宏,用来获取当前线程的地址。也将在后续分析。tsk存放的是线程结构体的地址。

/*

线程结构体原型如下在文件include/linux/sched.h

struct thread_info {
 struct task_struct *task;  /* main task structure */
 unsigned long  flags;
 struct exec_domain *exec_domain; /* execution domain */
 int   preempt_count; /* 0 => preemptable, <0 => BUG */
 __u32 cpu; /* should always be 0 on m68k */
 struct restart_block    restart_block;
};

*/
 get_thread_info tsk                                                                                             (2)
#ifdef CONFIG_PREEMPT

//TI_PREEMPT在文件arch\arm\kernel\asm-offsets.c中定义是线程结构体thread_info 的成员preempt_count

//结构体thread_info 中的偏移

/*

 内核态可剥夺内核,只有在 preempt_count 为 时, schedule() 才会被调用,其检查
是否需要进行进程切换,需要的话就切换。

*/
 ldr r8, [tsk, #TI_PREEMPT]  //获取preempt_count
 add r7, r8, #1   @ increment it //将该成员加一
 str r7, [tsk, #TI_PREEMPT] //间改变后的值存入preempt_count
#endif

 irq_handler  //调用中断操作函数,irq_handler是一个宏,在后续描述                               (3)
#ifdef CONFIG_PREEMPT
 ldr r0, [tsk, #TI_PREEMPT]
 str r8, [tsk, #TI_PREEMPT]
 teq r0, r7
 strne r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
 bl trace_hardirqs_on
#endif

 mov why, #0  //why在文件arch/arm/kernel/entry-header.S中定义为r8:why .req r8
 b ret_to_user  //返回到用户态,该宏在文件 linux/arch/arm/kernel/entry-common.S中定义。     (4)
 UNWIND(.fnend  )
ENDPROC(__irq_usr)

 

下面分别对上面四处宏进行分析。(usr_entryget_thread_info tskirq_handlerret_to_user

(1)

 .macro usr_entry
 UNWIND(.fnstart )
 UNWIND(.cantunwind ) @ don't unwind the user space

//S_FRAME_SIZE在文件arch\arm\kernel\asm-offsets.c中定义表示 寄存器结构体pt_regs的大小结构体

//pt_regs中有 r0~cpsr 18个寄存器即72个字节。
 sub sp, sp, #S_FRAME_SIZE  //为寄存器pt_regs结构体建立堆栈空间,让堆栈指针sp 指向r0 

//stmib为存储前加,所以此处留出了用于存储r0的空间,将r1 - r12存入堆栈。sp后没加!

//所以sp指向的堆栈位置没有变,一直指向用于存储r0的存储空间。

 stmib sp, {r1 - r12}

//将中断前r0lrspsr的值取出存放在r1 - r3中,此时的r0是作为堆栈的sp在使用的。

//它的值是指向中断前r0的值在堆栈中存放的位置。在寄存器结构体pt_regs在堆栈中的位置上面。

 ldmia r0, {r1 - r3}  

 //S_PC即是pt_regs中的PC寄存器位置,让r0指向该位置。虽然S_PC还没有存入堆栈但它在堆栈中的位置存在
 add r0, sp, #S_PC  

 mov r4, #-1   //r4中放入一个无效值。

 str r1, [sp]  //r1中存放的是中断前r0的值,此时将该值存入堆栈,上面已解释过在堆栈中流出r0的位置的问题。
 
//此时r2-r4存放的是中断前的lr, spsr的值和无效之。

 //此时将这些值存入pt_regs中寄存器在堆栈中对应的位置,即此时将中断前的lr, spsr的值和无效之

//存入寄存器结构体pt_regsARM_pc ARM_cpsrARM_ORIG_r0中。
 stmia r0, {r2 - r4}
 stmdb r0, {sp, lr}^ //stmdb是递减取值,将ARM_lrARM_sp存入lrsp中。


 alignment_trap r0

//宏 zero_fp在文件arch/arm/kernel/entry-header.S中定义,清零fp
 zero_fp
 .endm

 

上面的提到的struct pt_regs,在include/asm/ptrace.h中定义

struct pt_regs {
 long uregs[18];
};

#define ARM_cpsr uregs[16]
#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[17]

(2)

//宏macro get_thread_info在文件arch/arm/kernel/entry-header.S中定义。用来获取当前线程的地址。

/*

include/linux/sched.h中:

union thread_union {

    struct thread_info thread_info; // 线程属性

    unsigned long stack[THREAD_SIZE/sizeof(long)]; // 

};

由它定义的线程是8K字节对齐的, 并且在这8K的最低地址处存放的就是thread_info对象,即该栈拥有者线程的对象,而get_thread_info就是通过把sp13位清0(8K边 界)来获取当前thread_info对象的地址。

THREAD_SIZE在文件arch/arm/include/asm/thread_info.h中定义:#define THREAD_SIZE  8192

*/

 .macro get_thread_info, rd
 mov \rd, sp, lsr #13
 mov \rd, \rd, lsl #13
 .endm

(3)

//宏irq_handler文件arch/arm/kernel/entry-armv.S中定义:

 .macro irq_handler

//get_irqnr_preamble是一个空操作,在文件 arch/arm/mach-s3c2410/include/mach/entry-macro.S中定义
 get_irqnr_preamble r5, lr  

//get_irqnr_and_base通过读取寄存器INTPND来获得中断号。在该宏中获取的一些参量将存于这些寄存器中r0, r6, r5, lr

//get_irqnr_and_base定义在文件 arch/arm/mach-s3c2410/include/mach/entry-macro.S,这个宏后续讲到。
1: get_irqnr_and_base r0, r6, r5, lr  
 movne r1, sp
 @
 @ routine called with r0 = irq number, r1 = struct pt_regs *
 @
 adrne lr, 1b

/*

// 通过上面的宏get_irqnr_and_base为调用asm_do_IRQ准备了参数中断号。

于是调用asm_do_IRQ来处理中断。函数asm_do_IRQ()是中断处理函数的C语言入口。此函数将在后续讨论。

函数asm_do_IRQ()在文件linux/arch/arm/kernel/irq.c中实现。

*/
 bne asm_do_IRQ

#ifdef CONFIG_SMP
 。。。。。。
 
#endif

 .endm

get_irqnr_and_base是平台相关的,这个宏查询ISPR(IRQ挂起中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中;这个宏在不同的ARM芯片上是不一样的,这个宏主要作用在于就是获得发生中断的中断号,对于s3c2440,代码在arch/arm/mach-s3c2410/include/entry-macro.S里,该宏处理完后,r0 = 中断号。

 .macro get_irqnr_and_base, irqnr, irqstat, base, tmp

  mov \base, #S3C24XX_VA_IRQ

  @@ try the interrupt offset register, since it is there

  ldr \irqstat, [ \base, #INTPND ]
  teq \irqstat, #0
  beq 1002f
  ldr \irqnr, [ \base, #INTOFFSET ]
  mov \tmp, #1
  tst \irqstat, \tmp, lsl \irqnr
  bne 1001f

  @@ the number specified is not a valid irq, so try
  @@ and work it out for ourselves

  mov \irqnr, #0  @@ start here

  @@ work out which irq (if any) we got

  movs \tmp, \irqstat, lsl#16
  addeq \irqnr, \irqnr, #16
  moveq \irqstat, \irqstat, lsr#16
  tst \irqstat, #0xff
  addeq \irqnr, \irqnr, #8
  moveq \irqstat, \irqstat, lsr#8
  tst \irqstat, #0xf
  addeq \irqnr, \irqnr, #4
  moveq \irqstat, \irqstat, lsr#4
  tst \irqstat, #0x3
  addeq \irqnr, \irqnr, #2
  moveq \irqstat, \irqstat, lsr#2
  tst \irqstat, #0x1
  addeq \irqnr, \irqnr, #1

  @@ we have the value
1001:
  adds \irqnr, \irqnr, #IRQ_EINT0
1002:
  @@ exit here, Z flag unset if IRQ

 .endm

(4)

ret_to_user在文件arch/arm/kernel/entry-common.S下定义:

ENTRY(ret_to_user)
ret_slow_syscall:
 disable_irq    //禁止中断
 ldr r1, [tsk, #TI_FLAGS]  //获取线程结构体thread_union的flags成员
 tst r1, #_TIF_WORK_MASK  //判断task是否被阻塞
 bne work_pending     //根据需要进行进程的切换,该段代码在下面讲述。
no_work_pending:   //不需要进程切换
 /* perform architecture specific actions before user return */
 arch_ret_to_user r1, lr

 @ slow_restore_user_regs
 ldr r1, [sp, #S_PSR]  @ get calling cpsr
 ldr lr, [sp, #S_PC]!  @ get pc
 msr spsr_cxsf, r1   @ save in spsr_svc //// spsr里保存好被中断代码处的状态(cpsp)
 ldmdb sp, {r0 - lr}^   //恢复中断前寄存器的值恢复到各个寄存器。
 mov r0, r0
 add sp, sp, #S_FRAME_SIZE - S_PC
 movs pc, lr    //返回用户态
ENDPROC(ret_to_user)

arch/arm/kernel/entry-common.S

work_pending:
 tst r1, #_TIF_NEED_RESCHED //判断是否需要调度进程
 bne work_resched   // 进程调度
 tst r1, #_TIF_SIGPENDING
 beq no_work_pending //无需调度,返回
 mov r0, sp    @ 'regs'
 mov r2, why    @ 'syscall'
 bl do_notify_resume
 b ret_slow_syscall  @ Check work again

work_resched:
 bl schedule  //调用进程切换函数。

 

这里只讲了在用户模式下的中断处理,在内核模式下的处理方式也大抵相仿,就不再赘言了。

 

中断处理函数的C语言入口

 

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
 struct pt_regs *old_regs = set_irq_regs(regs);

 irq_enter(); //进入中断上下文


 if (irq >= NR_IRQS)
  handle_bad_irq(irq, &bad_irq_desc);
 else
  generic_handle_irq(irq); //根据中断号获取中断描述结构体,并调用其中断处理函数。


 irq_finish(irq); //退出中断上下文

 irq_exit();
 set_irq_regs(old_regs);
}

//函数generic_handle_irq()是函数generic_handle_irq_desc()的包装。

static inline void generic_handle_irq(unsigned int irq)
{
 generic_handle_irq_desc(irq, irq_to_desc(irq));
}

/*

如果实现了上层中断处理函数desc->handle_irq就调用它,实际上在中断处理函数s3c24xx_init_irq()中已为每一个

中断线分配了一个上层中断处理函数。

如果desc->handle_irq为空就调用通用中断处理函数__do_IRQ(irq);,在干函数中调用了函数handle_IRQ_event()

在函数handle_IRQ_event()中执行了该条中断线上的每一个中断例程。

*/

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
}

原创粉丝点击