字符设备驱动之笔记-中断处理过程情景分析

 

中断系统的运行过程分析:
--------------------------------------------------------------------------------------------------
硬件:
--------------------------------------------------------------------------------------------------
1. 按下/松开按键
2. CPU检测到中断信号,立刻/强制跳到中断向量入口
arch\arm\kernel\entry-armv.S
b vector_irq + stubs_offset


软件:
--------------------------------------------------------------------------------------------------
1. 保存被中断的现场(保存相关寄存器)
arch\arm\kernel\entry-armv.S

vector_stub irq, IRQ_MODE, 4    //name=irq mode=IRQ_MODE   correction=4


/*这段为内核中的源码,用上面的东西替换后,如后面的代码所示*/
.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)
 @
 stmia sp, {r0, lr}  @ save r0, lr
 mrs lr, spsr
 str lr, [sp, #8]  @ save spsr

 @
 @ Prepare for SVC32 mode.  IRQs remain disabled.
 @
 mrs r0, cpsr
 eor r0, r0, #(\mode ^ SVC_MODE)
 msr spsr_cxsf, r0

 @
 @ the branch table must immediately follow this code
 @
 and lr, lr, #0x0f
 mov r0, sp
 ldr lr, [pc, lr, lsl #2]
 movs pc, lr   @ branch to handler in SVC mode
 .endm


/*上面的代码被宏替换后的情况:*/
vector_irq:

 sub lr, lr, #4      /*根据arm9的5级流水线*/
 
 @
 @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
 @ (parent CPSR)
 @
 stmia sp, {r0, lr}  @ save r0, lr
 mrs lr, spsr
 str lr, [sp, #8]  @ save spsr

 @
 @ Prepare for SVC32 mode.  IRQs remain disabled.
 @
 mrs r0, cpsr
 eor r0, r0, #(\mode ^ SVC_MODE)
 msr spsr_cxsf, r0

 @
 @ the branch table must immediately follow this code
 @
 and lr, lr, #0x0f
 mov r0, sp
 ldr lr, [pc, lr, lsl #2]
 movs pc, lr   @ branch to handler in SVC mode
 .endm
/*接着执行下面的*/
 .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

假设是在用户态下发生了中断,执行__irq_usr
__irq_usr:
 usr_entry     // 保存被中断的现场

 .macro usr_entry  //这里是个宏,下面的代码主要工作就是保存现场
 sub sp, sp, #S_FRAME_SIZE
 stmib sp, {r1 - r12}

 ldmia r0, {r1 - r3}
 add r0, sp, #S_PC  @ here for interlock avoidance
 mov r4, #-1   @  ""  ""     ""        ""

 str r1, [sp]   @ save the "real" r0 copied
     @ from the exception stack

#if __LINUX_ARM_ARCH__ < 6 && !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)
#ifndef CONFIG_MMU
#warning "NPTL on non MMU needs fixing"
#else
 @ make sure our user space atomic helper is aborted
 cmp r2, #TASK_SIZE
 bichs r3, r3, #PSR_Z_BIT
#endif
#endif

 @
 @ We are now ready to fill in the remaining blanks on the stack:
 @
 @  r2 - lr_<exception>, already fixed up for correct return/restart
 @  r3 - spsr_<exception>
 @  r4 - orig_r0 (see pt_regs definition in ptrace.h)
 @
 @ Also, separately save sp_usr and lr_usr
 @
 stmia r0, {r2 - r4}
 stmdb r0, {sp, lr}^

 @
 @ Enable the alignment trap while in kernel mode
 @
 alignment_trap r0

 @
 @ Clear FP to mark the first stack frame
 @
 zero_fp
 .endm

/*上面保存完现场后,接着执行下面的代码*/
#ifdef CONFIG_TRACE_IRQFLAGS
 bl trace_hardirqs_off
#endif
 get_thread_info tsk
#ifdef CONFIG_PREEMPT
 ldr r8, [tsk, #TI_PREEMPT]  @ get preempt count
 add r7, r8, #1   @ increment it
 str r7, [tsk, #TI_PREEMPT]
#endif

 irq_handler
#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
 b ret_to_user

/*2. 接下来就是确定中断源*/
irq_handler
 .macro irq_handler
 get_irqnr_preamble r5, lr
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
 bne asm_do_IRQ     //中断处理程序的总入口函数,传入中断号,

/*3. 确定好中断源后,接着调用对应的处理函数:*/
bne asm_do_IRQ //到这里就会进入到C语言部分,根据中断号去判断是相应的哪个数组
   在asm_do_IRQ里面会执行下面的代码:
 struct irq_desc *desc = irq_desc + irq;  // irq_desc[irq]
 desc_handle_irq(irq, desc);
  desc->handle_irq(irq, desc); /* handle_edge_irq或handle_level_irq调用相应中断的中断处理入口函数,这个入口函数在初始化的时      候被指定了,如下面中断处理体系结构的初始化的过所示程分析*/
    

下面进入handle_edge_irq函数去看看!!!!!
--------------------------------------------------------------------------------------------------
handle_edge_irq
 // 统计中断发生的次数
 kstat_cpu(cpu).irqs[irq]++;
 
 // 响应中断,比如清除中断
 desc->chip->ack(irq);
 
 // 处理:
 action_ret = handle_IRQ_event(irq, action);
  do {
   ret = action->handler(irq, action->dev_id); /*到此真正的调用到你自己注册的中断服务程序,如buttons_irq*/
   if (ret == IRQ_HANDLED)
    status |= action->flags;
   retval |= ret;
   action = action->next;
  } while (action);

注意:由于有些是共享中断,如:IRQ_EINT8t23, 中断8-23是共享一个中断线的,对于这种情况,上面调用的只是他们的公共的中断服务程序,在中断服务程序中还用进一步的判断具体是哪个中断,最终调用到具体的中断服务程序。

set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
    desc->handle_irq = handle;    // irq_desc[IRQ_EINT8t23].handle_irq = s3c_irq_demux_extint8 // 确定更具体的中断号
    作用:重新计算终端号,进一步处理

进入s3c_irq_demux_extint8函数里面看看:
s3c_irq_demux_extint8
 irq += (IRQ_EINT4 - 4);   /*重新计算中断号*/
 desc_handle_irq(irq, irq_desc + irq);  /*调用到具体的中断服务程序*/
  desc->handle_irq(irq, desc);
                                                // 统计中断发生的次数
                                                kstat_cpu(cpu).irqs[irq]++;
                                                // 响应中断,比如清除中断
                                                desc->chip->ack(irq);
                                                // 处理:
                                                action_ret = handle_IRQ_event(irq, action);
                                                              do{
                                                                    ret = action->handler(irq, action->dev_id); /*到此共享中断也调用到了具体的中断服务程序了*/
                                                                    action = action->next;
                                                                } while (action);

 

中断注册过程分析:
--------------------------------------------------------------------------------------------------
注册方法,内核给我们提供了以个注册中断的接口request_irq,下面开始分析:
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
 
 // 分配/设置irqaction
 struct irqaction *action;
  action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
 // 设置它
  /*设置具体的中断服务程序,这个函数使我们驱动程序中要具体实现的*/
  action->handler = handler;
  action->flags = irqflags; /*中断标志*/
  cpus_clear(action->mask);
  action->name = devname;
  action->next = NULL;
  action->dev_id = dev_id; /*中断ID,这个有时候在驱动的中断服务程序中会用到,根据中断ID判断是哪个中断发生了*/
 
  /*将我们辛辛苦苦构建的irqaction结构体链接进入rq_desc[irq]数组的action链表中。等待以后被调用*/
  retval = setup_irq(irq, action);
   
free_irq:
 desc = irq_desc + irq;
 // 根据dev_id在链表里找到irqaction、
 for (;;) {
  struct irqaction **pp = p; 
  p = &action->next;
  if (action->dev_id != dev_id)
   continue;
  ............................................... 
  // 脱链
  *pp = action->next;
  // 如果链表空了:desc->chip->shutdown(irq);
  if (!desc->action) {
    desc->status |= IRQ_DISABLED;
    if (desc->chip->shutdown)
     desc->chip->shutdown(irq);
    else
     desc->chip->disable(irq);
   }
  ...............................................
   // 释放
      kfree(action);
 }
 
中断处理体系结构的初始化的过程分析:
--------------------------------------------------------------------------------------------------
由下面的内核启动过程可以看出,中断是在内核被初始化的。
在start_kernel里面调用了init_IRQ

start_kernel:
 init_IRQ

init_IRQ在内核中的具体源码如下所示:  
void __init init_IRQ(void)
{
 int irq;

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

 #ifdef CONFIG_SMP
  bad_irq_desc.affinity = CPU_MASK_ALL;
  bad_irq_desc.cpu = smp_processor_id();
 #endif
 init_arch_irq();
}

init_arch_irq()调用架构相关的中断初始化函数,对于S3C2410,S3C2440开发板,
这个函数就是S3C24XX_init_irq,此函数设置了处理函数入口(irq_desc[irq].handle_irq
它还为所有的中断设置了芯片相关的数据机构(irq_desc[irq].chip)

arch\arm\plat-s3c24xx\irq.c
void __init s3c24xx_init_irq(void)
 
 // IRQ_EINT4t7/IRQ_EINT8t23表示“一类中断”, linux内核首先确定发生的中断是哪个或哪类
 
 // 设置某类中断的"处理函数(不是最终的处理函数,它细分/调用)": irq_desc[IRQ_EINT4t7].handle_irq = s3c_irq_demux_extint4t7;
 set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
 
 // 设置某类中断的"处理函数(不是最终的处理函数,它细分/调用)": irq_desc[IRQ_EINT8t23].handle_irq = s3c_irq_demux_extint8;
 set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);

 //下面是设置具体中断的handle_irq
 for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++)
 {
  irqdbf("registering irq %d (ext int)\n", irqno);
  set_irq_chip(irqno, &s3c_irq_eint0t4);
  set_irq_handler(irqno, handle_edge_irq);
  set_irq_flags(irqno, IRQF_VALID);
 }
 
 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);
 }

 for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++)
 {
  irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
  set_irq_chip(irqno, &s3c_irq_uart1);
  set_irq_handler(irqno, handle_level_irq);
  set_irq_flags(irqno, IRQF_VALID);
 }

 for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++)
 {
  irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
  set_irq_chip(irqno, &s3c_irq_uart2);
  set_irq_handler(irqno, handle_level_irq);
  set_irq_flags(irqno, IRQF_VALID);
 }

 for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++)
 {
  irqdbf("registering irq %d (s3c adc irq)\n", irqno);
  set_irq_chip(irqno, &s3c_irq_adc);
  set_irq_handler(irqno, handle_edge_irq);
  set_irq_flags(irqno, IRQF_VALID);
 }

 

内核的启动过程分析:
--------------------------------------------------------------------------------------------------
1. arch/arm/kernel/head.S
head.s中有这样一段代码:这个是内核的总入口
 ENTRY(stext)
 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
      @ and irqs disabled
 mrc p15, 0, r9, c0, c0  @ get processor id
 bl __lookup_processor_type  @ r5=procinfo r9=cpuid
 movs r10, r5    @ invalid processor (r5=0)?
 beq __error_p   @ yes, error 'p'
 bl __lookup_machine_type  @ r5=machinfo
 movs r8, r5    @ invalid machine (r5=0)?
 beq __error_a   @ yes, error 'a'
 bl __create_page_tables
 /*
  * The following calls CPU specific code in a position independent
  * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
  * xxx_proc_info structure selected by __lookup_machine_type
  * above.  On return, the CPU will be ready for the MMU to be
  * turned on, and r0 will hold the CPU control register value.
  */
 ldr r13, __switch_data  @ address to jump to after
      @ mmu has been enabled
 adr lr, __enable_mmu  @ return (PIC) address
 add pc, r10, #PROCINFO_INITFUNC

对上面一段代码的分析:
 // 判断该内核是否能支持这个CPU
 bl __lookup_processor_type  @ r5=procinfo r9=cpuid
 
 // 判断该内核能否支持这个单板(machine)
 bl __lookup_machine_type  @ r5=machinfo

 // 创建页表
 bl __create_page_tables

 // 调用CPU相关的处理函数
 add pc, r10, #PROCINFO_INITFUNC

 // 上面的函数执行完后,跳到lr去(lr被设为"adr lr, __enable_mmu")
 // 使能MMU

 // 使能完MMU后,跳到r13去(r13被设为__mmap_switched)
 b start_kernel

下面我们去看看start_kernel:
start_kernel:
 
 // 解析bootloader传入的参数
 setup_arch(&command_line);
 //初始化中断,这个对上面的中断系统的运行过程分析非常有用。
    init_IRQ();

 接下来:
 // 调用一系列的驱动程序
 // 链接时,把一系列的驱动程序的函数放在一起
 // 然后从头执行到尾

 rest_init > kernel_init > do_basic_setup > do_initcalls
 
  for (call = __initcall_start; call < __initcall_end; call++) {
   ................................ 
   result = (*call)();
   ................................ 
  }

 extern initcall_t __initcall_start[], __initcall_end[];
 __initcall_start = .;
   .initcall.init : {
  INITCALLS
   }
   __initcall_end = .;

 #define INITCALLS      \
   *(.initcall0.init)      \
   *(.initcall0s.init)      \
   *(.initcall1.init)      \
   *(.initcall1s.init)      \
   *(.initcall2.init)      \
   *(.initcall2s.init)      \
   *(.initcall3.init)      \
   *(.initcall3s.init)      \
   *(.initcall4.init)      \
   *(.initcall4s.init)      \
   *(.initcall5.init)      \
   *(.initcall5s.init)      \
 *(.initcallrootfs.init)     \
   *(.initcall6.init)      \
   *(.initcall6s.init)      \
   *(.initcall7.init)      \
   *(.initcall7s.init)

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值