Chinaunix首页 | 论坛 | 博客
  • 博客访问: 345085
  • 博文数量: 201
  • 博客积分: 305
  • 博客等级: 二等列兵
  • 技术积分: 500
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-30 16:58
文章分类

全部博文(201)

文章存档

2015年(5)

2014年(12)

2013年(173)

2012年(11)

我的朋友

分类: 嵌入式

2013-04-09 02:19:16

本 文为速成教材,非精细研究,适合想了解linux中断机制,和从事linux驱动开发或者调式IRQ相关的驱动的工程师使用。该文章也没经过osboy的 review过,属于一遍速成之回忆录,所以会存在问题,请跟帖指出,我会一一回复,并整理出最终版本。注意转贴的时候你可以不加上作者的名字,但是最好 加上我的这些话,因为我们要对读文章的人负责人,谢谢。部分语言描述来源于网络拷贝。

1
Linux中断机制
1.1 认识三个重要的数据结构
1.1.1 中断描述符结构irq_desc

irq_desc
:中断全局描述符
struct irq_desc {
    unsigned int irq;注释:表示此中断描述符的中断号
    struct timer_rand_state *timer_rand_state;注释:timer伪随机数状态结构指针
    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;注释:中断的硬件chip级描述符,提供底层的硬件访问。具体见下面结构描述
    struct msi_desc *msi_desc;
    void *handler_data;
    void *chip_data;

    struct irqaction *action; /* IRQ action list */ 注释:标识当出现IRQ时要调用的中断服务例程。该字段指向IRQirqaction链表的第一个元素。
    unsigned int status; /* IRQ status */ 注释:描述IRQ线状态的一组标志
    unsigned int depth; /* nested irq disables */ 注释:如果IRQ线被激活,则显示0,如果IRQ线被禁止了不止一次,则显示一个正数。
    unsigned int wake_depth; /* nested wake enables */

    unsigned int irq_count; /* For detecting broken IRQs */
    unsigned long last_unhandled; /* Aging timer for unhandled count */
    unsigned int  irqs_unhandled;
    raw_spinlock_t lock;

#ifdef CONFIG_SMP
    cpumask_var_t affinity;
    const struct cpumask *affinity_hint;
    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;

Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每 个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了 中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。

1.1.2
中断服务描述符irqaction irqaction:每中断服务描述符
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_desc结构中的irqaction结构类型在include/linux/iterrupt.h中定义。用户注册的每个中断
处理函数用一个irqaction结构来表示,一个中断比如共享中断可以有多个处理函数,它们的irqaction结构链接成一个链表,以action为表头。他是在用户request_irq的时候第一次创建并被初始化。

1.1.3
中断硬件描述符irq_chip irq_chip结构类型是在include/linux/irq.h中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。所以我在这里称他为中断硬件描述符,主要是提供硬件操作的API
irq_chip:
struct irq_chip {
    const char *name; 注释:定义名字,通过 /proc/interrupts可以查看这个名字
    unsigned int (*startup)(unsigned int irq);
    void (*shutdown)(unsigned int irq);
    void (*enable)(unsigned int irq); ; 注释:使能IRQ,默认使能
    void (*disable)(unsigned int irq);
    void (*ack)(unsigned int irq); 注释irq的全局ack,需要根据硬件设置必须实现通常是清除当前中断使得可以接收下一个中断。
    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;
};

1.2
Request_irq开始说起我们知道用户驱动程序通过request_irq函数向内核注册中断处理函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的action链表添加一个表项。

   
error = request_irq(keypad->irq, keypad_irq_handler,IRQF_DISABLED, pdev->name, keypad);
    if (error) {
    dev_err(&pdev->dev, "failed to request IRQ\n");
    goto failed_put_clk;
    }

看上面这段代码是我们在写驱动的时候需要申请IRQ号的时候需要写的一段标准coderequest_irq的原型为:
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

所以我们写驱动的时候对应的各个参数:
keypad->irq unsigned int irq,中断号
keypad_irq_handler  irq_handler_t handler用户自定义的中断处理函数
IRQF_DISABLED  IRQF_DISABLED, linux定义的中断flags如下:

#define IRQF_DISABLED 0x00000020注释:当前irq处理的时候保持irq禁止
#define IRQF_SAMPLE_RANDOM 0x00000040
#define IRQF_SHARED 0x00000080注释:允许在多个设备之间共享irq
#define IRQF_PROBE_SHARED 0x00000100
#define IRQF_TIMER 0x00000200注释: 标志该中断是timer interrupt
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
pdev->name  const char *name
keypad  void *dev

request_irq调用了函数request_threaded_irq,他的原型为:
int request_threaded_irq(unsigned int irq, irq_handler_t handler,irq_handler_t thread_fn, unsigned long irqflags,const char *devname, void *dev_id)
{
    struct irqaction *action;
    struct irq_desc *desc;
    int retval;

/*
* 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; 注释:前面的log已经说的很明确,当多个设备共享irq的时候,我们必须通过dev_id来识别是哪一个设备来的中断,所以当flag被标志为:RQF_SHARED共享的时候,我们必须确保dev_id非空。

    desc = irq_to_desc(irq);

irq_to_desc
的原型如下:注释:
struct irq_desc *irq_to_desc(unsigned int irq)
{
    return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}
是通过irq号查找irq_desc数组,来确定对应irqirq_desc[irq].

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 = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
};
NR_IRQS一般会重定义在arch./arm/mach-xxxx/include/mach/irqs.h下面。

    if (!desc)

        return -EINVAL;注释:如果没找到相应的irq_desc表示出错了。

    if (desc->status & IRQ_NOREQUEST)
        return -EINVAL; 注释irq_desc.status初始化的时候设置为:IRQ_NOREQUEST不可被request状态,则这里做判断确保不能被request

    if (!handler) {
        if (!thread_fn)
            return -EINVAL;
        handler = irq_default_primary_handler;
}
注释:如果在驱动中request irq的时候没定义自己的handler,则至少thread_fn应该被定义否则报错返回EINVAL。同时这段code还赋值一个某人handler给驱动程序。

    action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
    if (!action)
        return -ENOMEM;
   
    action->handler = handler;
    action->thread_fn = thread_fn;
    action->flags = irqflags;
    action->name = devname;
    action->dev_id = dev_id;
    注释kzalloc一个irqaction结构体,并用驱动调用request_irq时候传进去的handlerirqflagsdevnamedev_id初始化这个结构体。

    chip_bus_lock(irq, desc);
注释:这个函数的原型:
/* Inline functions for support of irq chips on slow busses */
static inline void chip_bus_lock(unsigned int irq, struct irq_desc *desc)
{
    if (unlikely(desc->chip->bus_lock))
        desc->chip->bus_lock(irq);
}

linux给出的解释看出是为了在低速设备上的irq用的,就是实现了mutex。具体实现是在在定义arch/arm/mach-xxx/irq.c中定义:
static struct irq_chip xxx_irq_chip = {
    .ack = xxx_irq_ack,
    .mask = xxx_irq_mask,
    .unmask = xxx_irq_unmask,
}; 的时候有:
void (*bus_lock)(unsigned int irq);

void (*bus_sync_unlock)(unsigned int irq);
这两个函数的实现。

    retval = __setup_irq(irq, desc, action); 注释:setup irq函数是是一个重要的函数,我们将在下面进行具体讲解。
    chip_bus_sync_unlock(irq, desc);
    if (retval)
        kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ
    if (!retval && (irqflags & IRQF_SHARED)) {
        /*
        * It's a shared IRQ -- the driver ought to be prepared for it
        * to happen immediately, so let's make sure....
        * We disable the irq to make sure that a 'real' IRQ doesn't
        * run in parallel with our fake.
        */
        unsigned long flags;
        disable_irq(irq);
        local_irq_save(flags);
        handler(irq, dev_id);
        local_irq_restore(flags);
        enable_irq(irq);
    }
#endif
    return retval;
}
EXPORT_SYMBOL(request_threaded_irq);

__setup_irq函数:

/*
* Internal function to register an irqaction - typically used to
* allocate special interrupts that are part of the architecture.
*/
static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
    struct irqaction *old, **old_ptr;
    const char *old_name = NULL;
    unsigned long flags;
    int nested, shared = 0;
    int ret;

    if (!desc)
        return -EINVAL;

    if (desc->chip == &no_irq_chip)
        return -ENOSYS; 注释:这里判断chip是否被初始化,一般在arch/arm/mach-xxx/irq.cxxx_init_irq set_irq_chip(irqno, &xxx_irq_chip); 中初始化这个desc->chip指针,xxx_irq_chip定义的结构类似:
static struct irq_chip xxx_irq_chip = {
    .ack = xxx_irq_ack,
    .mask = xxx_irq_mask,
    .unmask = xxx_irq_unmask,
};

/*

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

    /* Oneshot interrupts are not allowed with shared */
    if ((new->flags & IRQF_ONESHOT) && (new->flags & IRQF_SHARED))
        return -EINVAL; 注释:Oneshot类型的中断不允许共享

    /*
    * Check whether the interrupt nests into another interrupt
    * thread.
    */
    nested = desc->status & IRQ_NESTED_THREAD;
    if (nested) {
        if (!new->thread_fn)
            return -EINVAL;
        /*
        * Replace the primary handler which was provided from
        * the driver for non nested interrupt handling by the
        * dummy function which warns when called.
        */
        new->handler = irq_nested_primary_handler;
    }

    /*
    * Create a handler thread when a thread function is supplied
    * and the interrupt does not nest into another interrupt
    * thread.
    */
    if (new->thread_fn && !nested) {
        struct task_struct *t;
        t = kthread_create(irq_thread, new, "irq/%d-%s", irq, new->name);
        if (IS_ERR(t))
            return PTR_ERR(t);
        /*
        * We keep the reference to the task struct even if
        * the thread dies to avoid that the interrupt code
        * references an already freed task_struct.
        */
        get_task_struct(t);
        new->thread = t;
    }注释:由于new->thread_fn在驱动调用的request irq函数中设置为NULL,所以以上代码执行不到,我们暂时不做解释。

    /*
    * The following block of code has to be executed atomically
    */
    raw_spin_lock_irqsave(&desc->lock, flags);
    old_ptr = &desc->action; 注释:old_ptrirqaction结构类型指针的指针。这里把old_ptr指向当前irqirq_desc成员action的指针的地址。
    old = *old_ptr; oldirqaction结构类型指针,这里是把old指向当前irqirq_desc成员action的指针。
    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 {
            old_ptr = &old->next;
            old = *old_ptr;
        } while (old);

    注释:将新建的irqaction结构链入irq_desc[irq]结构的action链表中,这有两种可能。如果action链表为空,则直接链入,否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致,即是否都声明为"可共享的"IRQF_SHARED)、是否都使用相同的触发方式,如果一致,则将新建的irqation结构链入。
        shared = 1;
    }

    if (!shared) {
        irq_chip_set_defaults(desc->chip); 注释:初始化一些默认的chip配置
        init_waitqueue_head(&desc->wait_for_threads);

   
    /* Setup the type (level, edge polarity) if configured: */
        if (new->flags & IRQF_TRIGGER_MASK) {
            ret = __irq_set_trigger(desc, irq, new->flags & IRQF_TRIGGER_MASK);
            if (ret)
                goto out_thread;
        } else
            compat_irq_chip_set_default_handler(desc);

#if defined(CONFIG_IRQ_PER_CPU)

        if (new->flags & IRQF_PERCPU)
            desc->status |= IRQ_PER_CPU;
#endif
            desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING | IRQ_ONESHOT | RQ_INPROGRESS | IRQ_SPURIOUS_DISABLED);
            if (new->flags & IRQF_ONESHOT)
                desc->status |= IRQ_ONESHOT;

            if (!(desc->status & IRQ_NOAUTOEN)) {
                desc->depth = 0;
                desc->status &= ~IRQ_DISABLED;
                desc->chip->startup(irq);
            } else
                /* Undo nested disables: */
                desc->depth = 1;

            /* Exclude IRQ from balancing if requested */
            if (new->flags & IRQF_NOBALANCING)
                desc->status |= IRQ_NO_BALANCING;

            /* Set default affinity mask once everything is setup */
            setup_affinity(irq, desc);
        } else if ((new->flags & IRQF_TRIGGER_MASK) && (new->flags & IRQF_TRIGGER_MASK) != (desc->status & IRQ_TYPE_SENSE_MASK)) {
            /* hope the handler works with the actual trigger mode... */
            pr_warning("IRQ %d uses trigger mode %d; requested %d\n",irq, (int)(desc->status & IRQ_TYPE_SENSE_MASK),
(int)(new->flags & IRQF_TRIGGER_MASK));

        }

        new->irq = irq;
        *old_ptr = new; 注释:把新建的irqation结构链入irq_desc中的IRQ action liststruct irqaction *action;)中。
      
        /* Reset broken irq detection when installing new handler */
        desc->irq_count = 0;
        desc->irqs_unhandled = 0;
注释:如果一个中断内核没有处理,那么这个中断就是意外中断,也就是说,与某个IRQ线相关的中断处理例程(ISR)不存在,或者与某个中断线相关的所有例程都识别不出是否是自己的硬件设备发出的中断。通常,内核检查从IRQ线接收的意外中断的数量,当这条IRQ线的有故障设备没完没了的发中断时,就禁用这条IRQ线,内核不会在每监测到一个意外中断时就立刻禁用IRQ线。由于几个设备可能共享IRQ线,更合适的办法是:内核把中断和意外中断的总次数分别放在irq_desc描述符的irq_countirqs_unhandled字段中,当第100000次中断产生时,如果意外中断的次数超过99900次内核才禁用这条IRQ线。

    /*
    * Check whether we disabled the irq via the spurious handler
    * before. Reenable it and give it another chance.
    */
    if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {
        desc->status &= ~IRQ_SPURIOUS_DISABLED;
        __enable_irq(desc, irq, false);
    }

    raw_spin_unlock_irqrestore(&desc->lock, flags);

    /*
    * Strictly no need to wake it up, but hung_task complains
    * when no hard interrupt wakes the thread up.
    */
    if (new->thread)
        wake_up_process(new->thread);
    register_irq_proc(irq, desc);
    new->dir = NULL;
    register_handler_proc(irq, new);
    return 0;

mismatch:
#ifdef CONFIG_DEBUG_SHIRQ
    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();
    }
#endif
    ret = -EBUSY;
out_thread:
    raw_spin_unlock_irqrestore(&desc->lock, flags);
    if (new->thread) {
        struct task_struct *t = new->thread;
        new->thread = NULL;
        if (likely(!test_bit(IRQTF_DIED, &new->thread_flags)))
            kthread_stop(t);
        put_task_struct(t);
    }
    return ret;
}

1.3
IRQ相关数据结构初始化硬件相关的操作接口的实现是在arch/arm/mach-xxx/irq.c中进行,类似于下面的例子,他一般初始化irq_chipirq_desc结构体的一些变量,并具体实现了操作硬件部分的函数。

static void xxx _irq_mask(unsigned int irq)
{
    __raw_writel(1 << irq, REG_AIC_MDCR);
}

static void xxx _irq_ack(unsigned int irq)
{
    __raw_writel(0x01, REG_AIC_EOSCR);
}

static void xxx _irq_unmask(unsigned int irq)
{
    __raw_writel(1 << irq, REG_AIC_MECR);
}

static struct irq_chip xxx _irq_chip = {
    .ack = xxx _irq_ack,
    .mask = xxx _irq_mask,
    .unmask = xxx _irq_unmask,
};

注释通过for循环遍历整理系统支持的irq中断号,NR_IRQS一般是定义最后一个irq+1Kernel/irq/chip.c中定义了一些函数用来初始化irq_chipirq_desc 中的成员变量,例如设置irq chipirq 触发类型和根据触发类型设置的irq handler

典型的使用方法是:

handle_level_irq—电平触发
handle_edge_irq—边沿触发

void __init xxx _init_irq(void)
{
    int irqno;

    for (irqno = IRQ_WDT; irqno < NR_IRQS; irqno++) {
        set_irq_chip(irqno, &xxx_irq_chip);注释:主要做的工作就是初始化desc->chip = chip;
        set_irq_handler(irqno, handle_level_irq);注释:主要做的工作就是初始化desc->handle_irq = handle; desc->name = name;
        set_irq_flags(irqno, IRQF_VALID); 注释:主要做的工作就是初始化desc->status
    }
}

那么xxx _init_irq(void)函数什么时候调用呢?这个还得看一个数据结构:
struct machine_desc {
    /*
    * Note! The first four elements are used
    * by assembler code in head.S, head-common.S
    */
    unsigned int nr;    /* architecture number    */
    unsigned int phys_io;  /* start of physical io*/
    unsigned int io_pg_offst; /* byte offset for io page tabe entry */
    const char *name; /* architecture name */
    unsigned long boot_params; /* tagged list */

    unsigned int video_start; /* start of video RAM */
    unsigned int video_end; /* end of video RAM */

    unsigned int reserve_lp0 :1; /* never has lp0 */
    unsigned int reserve_lp1 :1; /* never has lp1 */
    unsigned int reserve_lp2 :1; /* never has lp2 */
    unsigned int soft_reboot :1; /* soft reboot */

    void (*fixup)(struct machine_desc *, struct tag *, char **, struct meminfo *);
    void (*map_io)(void);/* IO mapping function */
    void (*init_irq)(void);
    struct sys_timer *timer; /* system tick timer */
    void (*init_machine)(void);
};

其中蓝色加粗函数就是定义了一个函数指针,一般来说我们在移植linux内核到某款arm板的时候我们需要实现一个针对板子初始化的第一个c文件mach-xxxboard.c,在这个c文件中我们需要实现以下code
MACHINE_START(XXXEVB, "XXXEVB")
/* Maintainer: Wan ZongShun */
    .phys_io = XXX_PA_UART,
    .io_pg_offst = (((u32)XXX_VA_UART) >> 18) & 0xfffc,
    .boot_params = 0,
    .map_io =xxxevb_map_io,
    .init_irq = xxx_init_irq,
    .init_machine = xxxevb_init,
    .timer = &xxx_timer,
MACHINE_END

我们知道linuxc语言第一个入口函数为asmlinkage void __init start_kernel(void)
在这个函数中有调用函数:
void __init init_IRQ(void)
{
    int irq;
    for (irq = 0; irq < NR_IRQS; irq++)
        irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE; 注释linux首先设置irq_desc[irq].status此时的状态为不可探测和不可request的。这个字段当然会被后续我们user的初始化重载的。

    init_arch_irq();
    该函数是在start_kernel(void)函数中的setup_arch(char **cmdline_p) 函数中有调用一段codeinit_arch_irq = mdesc->init_irq;,从而把我们的xxx_init_irq函数地址赋值给init_arch_irq,从而在这里开始就直接执行我们自己的xxx_init_irq函数了。
}

1.4 IRQ中断向量底层实现
异常,就是可以打断CPU正常运行流程的一些事情,比如外部中断、未定义指令、试图修改只读的数据、执行swi指令(Software Interrupt Instruction ,软件中断指令)等。当这些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。操作系统中经常通过异常来完成一些特定的功能。其中的中断也占有很大的一部分。例如下面的这几种情况:

CPU执行未定义的机器指令时将触发未定义指令异常,操作系统可以利用这个特点使用一些自定义的机器指令,它们在异常处理函数中实现。
当用户程序试图读写的数据或执行的指令不在内存中时,也会触发一个数据访问中止异常指令预取中止异常,在异常处理函数中将这些数据或指令读入内存,然后重新执行被中断的程序,这样可以节省内存,还使得操作系统可以运行这类程序,它们使用的内存远大于实际的物理内存。
在原先的内核版本中,内核在start_kernel函数(源码在init/main.c中)中调用trap_initinit_IRQ两个函数来设置异常和处理函数。在Linux最新的内核版本中,trap_init函数的内容发生了变化,在trap.c中:

void __init trap_init(void)
{
    return;
}
可见这个函数已经不起作用了,换成了early_trap_init();函数并且调用时间提前到了start kernel函数的setup_arch函数里面执行。

void __init early_trap_init(void)
{
    unsigned long vectors = CONFIG_VECTORS_BASE;
注释#define CONFIG_VECTORS_BASE 0xffff0000
首先理解下arm的中断向量,所谓向量,就是一些被安放在固定位置的代码,当发生异常时,CPU会自动执行这些固定位置上的指令。ARM架构的CPU的异常向量基址可以是0x00000000,也可以是0xffff0000,Linux内核使用后者。

    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);
注释:在entry-armv.S中:有code
    .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:

.globl
__vectors_start
export一个外部标号链接。

从上面这段
code可以看出,memcpy就是把上面这段code完整的拷贝到内存地址为:
0xffff0000的地址处。
而这里:
    .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

所以典型的
irq为例:
vector_irq + stubs_offset = __vectors_start + 0x200+ vector_irq - __stubs_start
__vectors_start这个标号应该是和pc相关的,所以他就表示当前的0xffff0000的地址。


    memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
    memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

__stubs_start, __stubs_end之间的代码是需要跳转的代码,他们被拷贝到vectors + 0x200地址处,也就是说__vectors_start + 0x200)表示的地址处。(vector_irq - __stubs_start)表示的是从vector_irq__stubs_start的相对地址,也就是偏移量。所以:
vector_irq + stubs_offset = __vectors_start + 0x200+ vector_irq - __stubs_start

表示从vectors + 0x200开始加上vector_irq相对于vectors + 0x200开始的偏移量,就是当前
vector_irq的实际地址。

这段跳转代码,我附在了下面:


/*
* 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));
    memcpy((void *)KERN_RESTART_CODE, syscall_restart_code, sizeof(syscall_restart_code));

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

中断跳转代码:

    .globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/

vector_stub irq, IRQ_MODE, 4
注释vector_stub的宏定义为:

    .macro vector_stub, name, mode, correction=0
    .align 5
vector_\name:
    .if \correction
        sub lr, lr, #\correction
    .endif
……………………………….
………………………………..

所以:
vector_stub irq IRQ_MODE,4相当于:
vector_irq:
    .if 4
        sub lr, lr, #4
    .endif
@
@ Save r0, lr_ (parent PC) and spsr_
@ (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 | PSR_ISETSTATE)


msr spsr_cxsf, r0

@

@ the branch table must immediately follow this code
@

and lr, lr, #0x0f


THUMB( adr r0, 1f)

THUMB( ldr lr, [r0, lr, lsl #2] )

mov r0, sp


ARM(ldrlr, [pc, lr, lsl #2])


movs pc, lr
@ branch to handler in SVC mode

ENDPROC(vector_\name)

    .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

/*
* Data abort dispatcher
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/

vector_stub dabt, ABT_MODE, 8

.long
__dabt_usr
@
0
(USR_26 / USR_32)

.long
__dabt_invalid
@
1
(FIQ_26 / FIQ_32)

.long
__dabt_invalid
@
2
(IRQ_26 / IRQ_32)

.long
__dabt_svc
@
3
(SVC_26 / SVC_32)

.long
__dabt_invalid
@
4

.long
__dabt_invalid
@
5

.long
__dabt_invalid
@
6

.long
__dabt_invalid
@
7

.long
__dabt_invalid
@
8

.long
__dabt_invalid
@
9

.long
__dabt_invalid
@
a

.long
__dabt_invalid
@
b

.long
__dabt_invalid
@
c

.long
__dabt_invalid
@
d

.long
__dabt_invalid
@
e

.long
__dabt_invalid
@
f

/*
* Prefetch abort dispatcher
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/

vector_stub
pabt, ABT_MODE, 4


.long
__pabt_usr
@
0 (USR_26 / USR_32)

.long
__pabt_invalid
@
1 (FIQ_26 / FIQ_32)

.long
__pabt_invalid
@
2 (IRQ_26 / IRQ_32)

.long
__pabt_svc
@
3 (SVC_26 / SVC_32)

.long
__pabt_invalid
@
4

.long
__pabt_invalid
@
5

.long
__pabt_invalid
@
6

.long
__pabt_invalid
@
7

.long
__pabt_invalid
@
8

.long
__pabt_invalid
@
9

.long
__pabt_invalid
@
a

.long
__pabt_invalid
@
b

.long
__pabt_invalid
@
c

.long
__pabt_invalid
@
d

.long
__pabt_invalid
@
e

.long
__pabt_invalid
@
f

/*
* Undef instr entry dispatcher
* Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*/

vector_stub
und, UND_MODE


.long
__und_usr
@
0 (USR_26 / USR_32)

.long
__und_invalid
@
1 (FIQ_26 / FIQ_32)

.long
__und_invalid
@
2 (IRQ_26 / IRQ_32)

.long
__und_svc
@
3 (SVC_26 / SVC_32)

.long
__und_invalid
@
4

.long
__und_invalid
@
5

.long
__und_invalid
@
6

.long
__und_invalid
@
7

.long
__und_invalid
@
8

.long
__und_invalid
@
9

.long
__und_invalid
@
a

.long
__und_invalid
@
b

.long
__und_invalid
@
c

.long
__und_invalid
@
d

.long
__und_invalid

@
e

.long
__und_invalid
@
f

.align
5

/*=============================================================================
* Undefined FIQs
*-----------------------------------------------------------------------------
* Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC
* MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg.
* Basically to switch modes, we *HAVE* to clobber one register...
brain
* damage alert!
I don't think that we can execute any code in here in any
* other mode than FIQ...
Ok you can switch to another mode, but you can't
* get out of that mode without clobbering one register.
*/
vector_fiq:

disable_fiq

subs pc, lr, #4

/*=============================================================================
* Address exception handler
*-----------------------------------------------------------------------------
* These aren't too critical.
* (they're not supposed to happen, and won't happen in 32-bit data mode).
*/

vector_addrexcptn:

b vector_addrexcptn

/*
* We group all the following data together to optimise
* for CPUs with separate I & D caches.
*/

.align
5

.LCvswi:

.word
vector_swi


.globl
__stubs_end
__stubs_end:

.equ
stubs_offset, __vectors_start + 0x200 - __stubs_start

__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

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

ldr r0, [tsk, #TI_FLAGS]
@ get flags

teq r8, #0
@ if preempt count != 0

movne r0, #0
@ force flags to 0

tst r0, #_TIF_NEED_RESCHED

blne svc_preempt
#endif

ldr r4, [sp, #S_PSR]
@ irqs are already disabled
#ifdef CONFIG_TRACE_IRQFLAGS
tst r4, #PSR_I_BIT
bleq trace_hardirqs_on
#endif

svc_exit r4
@ return from exception

UNWIND(.fnend
)
ENDPROC(__irq_svc)

.macro
irq_handler

get_irqnr_preamble r5, lr
1:
get_irqnr_and_base r0, r6, r5, lr
注释:该宏定义在:entry-macro.S中,给r0赋值当前发生中断的中断号。

.macro
get_irqnr_and_base, irqnr, irqstat, base, tmp


mov
\base, #AIC_BA


ldr
\irqnr, [ \base, #AIC_IPER]


ldr
\irqnr, [ \base, #AIC_ISNR]


cmp
\irqnr, #0


.endm

movne r1, sp

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

adrne lr, BSYM(1b)

bne asm_do_IRQ
注释:当调用asm_do_IRQ的时候,传进去的参数为r0 = irq number, r1 = struct pt_regs *
R0就是刚才从get_irqnr_and_base得到的,r1sp
asm_do_IRQ定义在:linux/arch/arm/kernel/irq.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();

    /*
    * 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);
注释:这里最终会调用到desc->handle_irq(irq, desc);,也就是我们在arch/arm/mach-xxx/irq,c中初始化的handle_level_irq
通过这句set_irq_handler(irqno, handle_level_irq);来初始化的。
handle_level_irq最终又会调用到ret = action->handler(irq, action->dev_id);从而调用真正的用户注册的中断处理函数,该中断处理函数是通过request_irq函数注册的。

    }

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

    irq_exit();
    set_irq_regs(old_regs);
}

#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, BSYM(1b)

bne do_IPI

#ifdef CONFIG_LOCAL_TIMERS
    test_for_ltirq r0, r6, r5, lr
    movne r0, sp
    adrne lr, BSYM(1b)
     bne do_local_timer
#endif
#endif


.endm
阅读(388) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~