Chinaunix首页 | 论坛 | 博客
  • 博客访问: 488741
  • 博文数量: 76
  • 博客积分: 5196
  • 博客等级: 大校
  • 技术积分: 1414
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-10 18:43
个人简介

转了个圈,又回来了

文章分类

全部博文(76)

文章存档

2013年(1)

2011年(8)

2010年(9)

2009年(22)

2008年(36)

我的朋友

分类: LINUX

2011-04-05 11:30:45

一.Linux中断向量拷贝

首先说明Linux的异常中断处理。Linux为了正确的响应中断,首先会将ARM处理器

8个异常中断处理程序的入口安装到各自对应的中断向量地址中。

中断向量地址又分为低端中断向量地址模式(0)和高端中断向量地址模式(0Xffff0000)

具体是高端还是低端,这个由具体的处理器来确定。

Linux中中断向量地址的拷贝由trap_init函数完成,下面的源代码的介绍都是来自于

freescaleimx27linux2.6.19的源代码包。

 

Start_kernel -> trap_init

 

void __init trap_init(void)

{

              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;

 

              /*

               * Copy the vectors, stubs and kuser helpers (in entry-armv.S)

               * into the vector page, mapped at 0xffff0000, and ensure these

               * are visible to the instruction stream.

               */

              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);

 

              /*

               * Copy signal return handlers into the vector page, and

               * set sigreturn to be a pointer to these.

               */

              memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,

                     sizeof(sigreturn_codes));

 

              flush_icache_range(vectors, vectors + PAGE_SIZE);

              modify_domain(DOMAIN_USER, DOMAIN_CLIENT);

}

Trap_init函数的主要功能就是上面的3memcpy函数,讲中断处理程序的入口拷贝到中断向量地址。其中

         extern char __stubs_start[], __stubs_end[];

              extern char __vectors_start[], __vectors_end[];

              extern char __kuser_helper_start[], __kuser_helper_end[];

这三个变量是在汇编源文件中定义的,在源代码包里定义在entry-armv.S中。

__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:

我们重点关注 中断处理。在中断前期处理函数中会根据IRQ产生时所处的模式来跳转到不同的中断处理流程中。

 

Linux中断处理

ARM处理器的中断是由处理器内部或者外部的中断源产生,通过IRQ或者FIQ中断请求路径传递给处理器。可见在ARM模式下中断可以配成IRQ模式或者FIQ模式。但是在Linux系统里面,所有的中断源都被配成了IRQ中断模式。

 

根据产生IRQ中断时系统所处的模式,可以分为内核态中断和用户态中断。当IRQ中断发生时,系统正在运行内核程序,这时ARM芯片运行在超级用户模式,它对应的中断处理程序是__irq_svc();当IRQ中断发生时,系统处于用户模式,它对应的中断处理程序是__irq_usr();两者之间的区别主要是在中断返回的时候的处理不同。

1.       Irq_svc

内核模式下,从中断返回的时候,只有当前系统支持抢占的时候才会去检查当前的进程是否需要重新调度。

              .align   5

__irq_svc:

              svc_entry

 

#ifdef CONFIG_TRACE_IRQFLAGS

              bl           trace_hardirqs_off

#endif

#ifdef CONFIG_PREEMPT

              get_thread_info tsk

              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_FLAGS]                @ get flags

              tst         r0, #_TIF_NEED_RESCHED

              blne      svc_preempt

preempt_return:

              ldr         r0, [tsk, #TI_PREEMPT]                        @ read preempt value

              str         r8, [tsk, #TI_PREEMPT]                        @ restore preempt count

              teq        r0, r7

              strne    r0, [r0, -r0]                                   @ bug()

#endif

              ldr         r0, [sp, #S_PSR]                         @ irqs are already disabled

              msr       spsr_cxsf, r0

#ifdef CONFIG_TRACE_IRQFLAGS

              tst         r0, #PSR_I_BIT

              bleq      trace_hardirqs_on

#endif

              ldmia   sp, {r0 - pc}^                                @ load r0 - pc, cpsr

 

              .ltorg

 

#ifdef CONFIG_PREEMPT

svc_preempt:

              teq        r8, #0                                             @ was preempt count = 0

              ldreq    r6, .LCirq_stat

              movne               pc, lr                                               @ no

              ldr         r0, [r6, #4]                                   @ local_irq_count

              ldr         r1, [r6, #8]                                   @ local_bh_count

              adds     r0, r0, r1

              movne               pc, lr

              mov      r7, #0                                             @ preempt_schedule_irq

              str         r7, [tsk, #TI_PREEMPT]                        @ expects preempt_count == 0

1:          bl           preempt_schedule_irq                          @ irq en/disable is done inside

              ldr         r0, [tsk, #TI_FLAGS]                @ get new tasks TI_FLAGS

              tst         r0, #_TIF_NEED_RESCHED

              beq       preempt_return                                       @ go again

              b            1b

#endif

2.       Irq_usr

在从中断返回的时候,无论系统是否支持强占,都需要检查当前的进程是否需要重新调度,另外还需要检查是否有信号需要处理。

 

__irq_usr:

              usr_entry

 

#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

 

3.       Irq_handler

上述的两个中断处理函数中实际上进行中断处理函数是irq_handler

      .macro              irq_handler

1:  get_irqnr_and_base r0, r6, r5, lr

@获取当前还未处理的中断号,存放在r0中。

      movne               r1, sp

      @

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

      @

      adrne   lr, 1b

      bne       asm_do_IRQ

@调用函数处理中断

 

#ifdef CONFIG_SMP

      /*

       * XXX

       *

       * this macro assumes that irqstat (r6) and base (r5) are

       * preserved from get_irqnr_and_base above

       */

      test_for_ipi r0, r6, r5, lr

      movne               r0, sp

      adrne   lr, 1b

      bne       do_IPI

 

#ifdef CONFIG_LOCAL_TIMERS

      test_for_ltirq r0, r6, r5, lr

      movne               r0, sp

      adrne   lr, 1b

      bne       do_local_timer

#endif

#endif

 

  .endm

 

4.       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 asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

{

      struct pt_regs *old_regs = set_irq_regs(regs);

      struct irqdesc *desc = irq_desc + irq;

 

      /*

       * Some hardware gives randomly wrong interrupts.  Rather

       * than crashing, do something sensible.

       */

      if (irq >= NR_IRQS)

                    desc = &bad_irq_desc;

 

      irq_enter(); //将当前被中断进程加上正在进行IRQ中断处理的标志位。

 

#ifdef CONFIG_CODETEST

      ct_isr_enter(irq);

#endif /* CONFIG_CODETEST */

 

      desc_handle_irq(irq, desc);

 

      /* AT91 specific workaround */

      irq_finish(irq);

 

      irq_exit();//检查是否有需要处理的软中断,

#ifdef CONFIG_CODETEST

      ct_isr_exit(irq);

#endif /* CONFIG_CODETEST */

      set_irq_regs(old_regs);

}

 

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)

{

      desc->handle_irq(irq, desc);

}

 

5.       struct irq_desc -> handle_irq

这里追踪到结构体irq_desc 下的 handle_irq函数。这里首先介绍下struct irq_desc.

        Struct irq_desc irq_desc[NR_IRQS];结构体数组,对于系统中的每一个中断号都对应一个struct irq_desc结构体。其中两个关键的成员是:

struct irq_desc {

              …………………………………………….

              irq_flow_handler_t     handle_irq;

              struct irqaction             *action;            /* IRQ action list */

              ………………………………………………

}

handle_irq 中断请求处理指针,所有中断结构体中的这个函数都是一样的。Action这个变量就涉及到具体的处理函数了。

struct irqaction {

              irq_handler_t handler;

              unsigned long flags;

              cpumask_t mask;

              const char *name;

              void *dev_id;

              struct irqaction *next;

              int irq;

              struct proc_dir_entry *dir;

};

言归正传,上面说到会执行handle_irq这个函数,那这个函数又是怎么来的呢,暂时先不管,只给出该函数的实现:handle_IRQ_event

 

/**

 * handle_IRQ_event - irq action chain handler

 * @irq:           the interrupt number

 * @action:    the interrupt action chain for this irq

 *

 * Handles the action chain of an irq event

 */

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)

{

              irqreturn_t ret, retval = IRQ_NONE;

              unsigned int status = 0;

 

              handle_dynamic_tick(action);

 

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

                            local_irq_enable_in_hardirq();

 

              do {

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

                            if (ret == IRQ_HANDLED)

                                          status |= action->flags;

                            retval |= ret;

                            action = action->next;

              } while (action);

 

              if (status & IRQF_SAMPLE_RANDOM)

                            add_interrupt_randomness(irq);

              local_irq_disable();

 

              return retval;

}

可以看到,核心部分在与执行注册在该中断上面的函数。

 

6.中断处理程序的安装与卸载 Request_irq  free_irq

这里中断处理程序就是指的上一节中提到的action->handler

 

/**

 *         request_irq - allocate an interrupt line

 *         @irq: Interrupt line to allocate

 *         @handler: Function to be called when the IRQ occurs

 *         @irqflags: Interrupt type flags

 *         @devname: An ascii name for the claiming device

 *         @dev_id: A cookie passed back to the handler function

 *

 *         This call allocates interrupt resources and enables the

 *         interrupt line and IRQ handling. From the point this

 *         call is made your handler function may be invoked. Since

 *         your handler function must clear any interrupt the board

 *         raises, you must take care both to initialise your hardware

 *         and to set up the interrupt handler in the right order.

 *

 *         Dev_id must be globally unique. Normally the address of the

 *         device data structure is used as the cookie. Since the handler

 *         receives this value it makes sense to use it.

 *

 *         If your interrupt is shared you must pass a non NULL dev_id

 *         as this is required when freeing the interrupt.

 *

 *         Flags:

 *

 *         IRQF_SHARED                           Interrupt is shared

 *         IRQF_DISABLED          Disable local interrupts while processing

 *         IRQF_SAMPLE_RANDOM     The interrupt can be used for entropy

 *

 */

int request_irq(unsigned int irq, irq_handler_t handler,

                            unsigned long irqflags, const char *devname, void *dev_id)

{

              struct irqaction *action;

              int retval;

 

#ifdef CONFIG_LOCKDEP

              /*

               * Lockdep wants atomic interrupt handlers:

               */

              irqflags |= SA_INTERRUPT;

#endif

              /*

               * Sanity-check: shared interrupts must pass in a real dev-ID,

               * otherwise we'll have trouble later trying to figure out

               * which interrupt is which (messes up the interrupt freeing

               * logic etc).

               */

              if ((irqflags & IRQF_SHARED) && !dev_id)

                            return -EINVAL;

              if (irq >= NR_IRQS)

                            return -EINVAL;

              if (irq_desc[irq].status & IRQ_NOREQUEST)

                            return -EINVAL;

              if (!handler)

                            return -EINVAL;

 

              action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);

              if (!action)

                            return -ENOMEM;

 

              action->handler = handler;

              action->flags = irqflags;

              cpus_clear(action->mask);

              action->name = devname;

              action->next = NULL;

              action->dev_id = dev_id;

 

              select_smp_affinity(irq);

 

              retval = setup_irq(irq, action);

              if (retval)

                            kfree(action);

 

              return retval;

}

 

Request_irq函数首先申请了一个struct irqaction的结构体,然后给该结构体。然后将实参赋值给该结构体。最后调用setup_irq注册该结构体。

 

/*

 * Internal function to register an irqaction - typically used to

 * allocate special interrupts that are part of the architecture.

 */

int setup_irq(unsigned int irq, struct irqaction *new)

{

      struct irq_desc *desc = irq_desc + irq;

      struct irqaction *old, **p;

      const char *old_name = NULL;

      unsigned long flags;

      int shared = 0;

 

      if (irq >= NR_IRQS)

                    return -EINVAL;

 

      if (desc->chip == &no_irq_chip)

                    return -ENOSYS;

      /*

       * Some drivers like serial.c use request_irq() heavily,

       * so we have to be careful not to interfere with a

       * running system.

       */

      if (new->flags & IRQF_SAMPLE_RANDOM) {

                    /*

                     * This function might sleep, we want to call it first,

                     * outside of the atomic block.

                     * Yes, this might clear the entropy pool if the wrong

                     * driver is attempted to be loaded, without actually

                     * installing a new handler, but is this really a problem,

                     * only the sysadmin is able to do this.

                     */

                    rand_initialize_irq(irq);

      }

 

      /*

       * The following block of code has to be executed atomically

       */

      spin_lock_irqsave(&desc->lock, flags);

      p = &desc->action;

      old = *p;

      if (old) {

                    /*

                     * Can't share interrupts unless both agree to and are

                     * the same type (level, edge, polarity). So both flag

                     * fields must have IRQF_SHARED set and the bits which

                     * set the trigger type must match.

                     */

                    if (!((old->flags & new->flags) & IRQF_SHARED) ||

                        ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {

                                  old_name = old->name;

                                  goto mismatch;

                    }

 

#if defined(CONFIG_IRQ_PER_CPU)

                    /* All handlers must agree on per-cpuness */

                    if ((old->flags & IRQF_PERCPU) !=

                        (new->flags & IRQF_PERCPU))

                                  goto mismatch;

#endif

 

                    /* add new interrupt at end of irq queue */

                    do {

                                  p = &old->next;

                                  old = *p;

                    } while (old);

                    shared = 1;

      }

 

      *p = new;

#if defined(CONFIG_IRQ_PER_CPU)

      if (new->flags & IRQF_PERCPU)

                    desc->status |= IRQ_PER_CPU;

#endif

      if (!shared) {

                    irq_chip_set_defaults(desc->chip);

 

                    /* Setup the type (level, edge polarity) if configured: */

                    if (new->flags & IRQF_TRIGGER_MASK) {

                                  if (desc->chip && desc->chip->set_type)

                                                desc->chip->set_type(irq,

                                                                            new->flags & IRQF_TRIGGER_MASK);

                                  else

                                                /*

                                                 * IRQF_TRIGGER_* but the PIC does not support

                                                 * multiple flow-types?

                                                 */

                                                printk(KERN_WARNING "No IRQF_TRIGGER set_type "

                                                       "function for IRQ %d (%s)\n", irq,

                                                       desc->chip ? desc->chip->name :

                                                       "unknown");

                    } else

                                  compat_irq_chip_set_default_handler(desc);

 

                    desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |

                                                  IRQ_INPROGRESS);

 

                    if (!(desc->status & IRQ_NOAUTOEN)) {

                                  desc->depth = 0;

                                  desc->status &= ~IRQ_DISABLED;

                                  if (desc->chip->startup)

                                                desc->chip->startup(irq);

                                  else

                                                desc->chip->enable(irq);

                    } else

                                  /* Undo nested disables: */

                                  desc->depth = 1;

      }

      spin_unlock_irqrestore(&desc->lock, flags);

 

      new->irq = irq;

      register_irq_proc(irq);

      new->dir = NULL;

      register_handler_proc(irq, new);

 

      return 0;

 

mismatch:

      if (!(new->flags & IRQF_PROBE_SHARED)) {

                    printk(KERN_ERR "IRQ handler type mismatch for IRQ %d\n", irq);

                    if (old_name)

                                  printk(KERN_ERR "current handler: %s\n", old_name);

                    dump_stack();

      }

      spin_unlock_irqrestore(&desc->lock, flags);

      return -EBUSY;

}

阅读(5322) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~

scarecrow_wang2014-03-29 16:45:27

有个问题啊, 在asm_do_IRQ()之前硬件会关中断么?即对CPSR中的I位写1?

scarecrow_wang2014-03-29 16:40:04

厉害!