linux中断处理流程

一.重要数据结构

1.irq_desc

内核中记录一个irq_desc的数组,数组的每一项对应一个中断或者一组中断(使用同一中断号)。一句话,irq_desc几乎记录所有中断相关的东西,这个结构是中断的核心。其中包括两个重要的数据结构irq_chip和irqaction。

include/linux/irq.h

struct irq_desc irq_desc[NR_IRQS];

/**

 * struct irq_desc - interrupt descriptor

 * @irq:        interrupt number for this descriptor

 * @timer_rand_state:   pointer to timer rand state struct

 * @kstat_irqs:     irq stats per cpu

 * @irq_2_iommu:    iommu with this irq

 * @handle_irq:     highlevel irq-events handler [if NULL, __do_IRQ()]

 * @chip:       low level interrupt hardware access

 * @msi_desc:       MSI descriptor

 * @handler_data:   per-IRQ data for the irq_chip methods

 * @chip_data:      platform-specific per-chip private data for the chip

 *          methods, to allow shared chip implementations

 * @action:     the irq action chain

 * @status:     status information

 * @depth:      disable-depth, for nested irq_disable() calls

 * @wake_depth:     enable depth, for multiple set_irq_wake() callers

 * @irq_count:      stats field to detect stalled irqs

 * @last_unhandled: aging timer for unhandled count

 * @irqs_unhandled: stats field for spurious unhandled interrupts

 * @lock:       locking for SMP

 * @affinity:       IRQ affinity on SMP     //affinity 密切关系,姻亲关系

 * @cpu:        cpu index useful for balancing

 * @pending_mask:   pending rebalanced interrupts

 * @threads_active: number of irqaction threads currently running

 * @wait_for_threads:   wait queue for sync_irq to wait for threaded handlers

 * @dir:        /proc/irq/ procfs entry

 * @name:       flow handler name for /proc/interrupts output

 */

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

    irq_flow_handler_t  handle_irq;

    struct irq_chip     *chip;

    struct msi_desc     *msi_desc;

    void            *handler_data;

    void            *chip_data;

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

    unsigned int        status;     /* IRQ status */

 

    unsigned int        depth;      /* nested irq disables */

    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;

    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;

#endif

    const char      *name;

} ____cacheline_internodealigned_in_smp;
struct irq_desc irq_desc[NR_IRQS]

2.irq_chip

irq_chip里面基本上是一些回调函数,其中大多用于操作系统底层硬件,设置寄存器,其中设置GPIO为中断输入就是其中一个回调函数。

这是一个框架,需要我们进一步填充里面的函数。

include/linux/irq.h

/**

 * struct irq_chip - hardware interrupt chip descriptor

 *

 * @name:       name for /proc/interrupts

 * @startup:        start up the interrupt (defaults to ->enable if NULL)

 * @shutdown:       shut down the interrupt (defaults to ->disable if NULL)

 * @enable:     enable the interrupt (defaults to chip->unmask if NULL)

 * @disable:        disable the interrupt (defaults to chip->mask if NULL)

 * @ack:        start of a new interrupt

 * @mask:       mask an interrupt source

 * @mask_ack:       ack and mask an interrupt source

 * @unmask:     unmask an interrupt source

 * @eoi:        end of interrupt - chip level

 * @end:        end of interrupt - flow level

 * @set_affinity:   set the CPU affinity on SMP machines

 * @retrigger:      resend an IRQ to the CPU

 * @set_type:       set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ

 * @set_wake:       enable/disable power-management wake-on of an IRQ

 *

 * @release:        release function solely used by UML

 * @typename:       obsoleted by name, kept as migration helper

 */

struct irq_chip {

    const char  *name;

    unsigned int    (*startup)(unsigned int irq);

    void        (*shutdown)(unsigned int irq);

    void        (*enable)(unsigned int irq);

    void        (*disable)(unsigned int irq);

 

    void        (*ack)(unsigned int irq);    //中断应答函数,就是清除中断标识函数

    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);     //中断流程结束

    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);  //设置中断类型,其中包括设置                                                                                                                        //GPIO口为中断输入

    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;

};
struct irq_chip

3.irqaction

我们用irq_request函数注册中断时,主要做两件事情,根据中断号生成一个irqaction结构并添加到irq_desc中的action结构链表,另一方面做一些初始化的工作,其中包括设置中断触发方式,设置一些irq_chip结构中没有初始化的函数为默认,开启中断,设置GPIO口为中断输入模式。

特别说明irqflags,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)

include/linux/interrupt.h

//中断标记flag

/*

 * These correspond to the IORESOURCE_IRQ_* defines in

 * linux/ioport.h to select the interrupt line behaviour.  When

 * requesting an interrupt without specifying a IRQF_TRIGGER, the

 * setting should be assumed to be "as already configured", which

 * may be as per machine or firmware initialisation.

 */

#define IRQF_TRIGGER_NONE   0x00000000

#define IRQF_TRIGGER_RISING 0x00000001

#define IRQF_TRIGGER_FALLING    0x00000002

#define IRQF_TRIGGER_HIGH   0x00000004

#define IRQF_TRIGGER_LOW    0x00000008

#define IRQF_TRIGGER_MASK   (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \

                 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)

#define IRQF_TRIGGER_PROBE  0x00000010

 

/*

 * These flags used only by the kernel as part of the

 * irq handling routines.

 *

 * IRQF_DISABLED - keep irqs disabled when calling the action handler

 * IRQF_SAMPLE_RANDOM - irq is used to feed the random generator

 * IRQF_SHARED - allow sharing the irq among several devices

 * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur

 * IRQF_TIMER - Flag to mark this interrupt as timer interrupt

 * IRQF_PERCPU - Interrupt is per cpu

 * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing

 * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is

 *                registered first in an shared interrupt is considered for

 *                performance reasons)

 */

#define IRQF_DISABLED       0x00000020

#define IRQF_SAMPLE_RANDOM  0x00000040

#define IRQF_SHARED     0x00000080

#define IRQF_PROBE_SHARED   0x00000100

#define IRQF_TIMER      0x00000200

#define IRQF_PERCPU     0x00000400

#define IRQF_NOBALANCING    0x00000800

#define IRQF_IRQPOLL        0x00001000

 

/*

 * Bits used by threaded handlers:

 * IRQTF_RUNTHREAD - signals that the interrupt handler thread should run

 * IRQTF_DIED      - handler thread died

 * IRQTF_WARNED    - warning "IRQ_WAKE_THREAD w/o thread_fn" has been printed

 */

enum {

    IRQTF_RUNTHREAD,

    IRQTF_DIED,

    IRQTF_WARNED,

};

 

typedef irqreturn_t (*irq_handler_t)(int, void *);

 

/**

 * struct irqaction - per interrupt action descriptor

 * @handler:    interrupt handler function

 * @flags:  flags (see IRQF_* above)

 * @mask:   no comment as it is useless and about to be removed

 * @name:   name of the device

 * @dev_id: cookie to identify the device

 * @next:   pointer to the next irqaction for shared interrupts

 * @irq:    interrupt number

 * @dir:    pointer to the proc/irq/NN/name entry

 * @thread_fn:  interupt handler function for threaded interrupts

 * @thread: thread pointer for threaded interrupts

 * @thread_flags:   flags related to @thread

 */

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;

    irq_handler_t thread_fn;

    struct task_struct *thread;

    unsigned long thread_flags;

};
struct irqaction

二.中断流程

整个中断可以分为三大流程

>>中断初始化流程  注意这个阶段就是非常迷惑的一个阶段,很多初始化,设置寄存器等等问题都是内核启动的时候就已经初始化了,这个阶段做很多工作,其中最重要的就是初始化了irq_chip结构。使得其中的众多函数已经设置好了,可以被调用了。注意这里只是实现了irq_chip结构的函数,要响应中断还有很多事情要做。一般在板卡board文件MACHINE_START中设置,启动过程中执行

>>中断注册流程   每次用中断的时候就是注册一个中断函数。request_irq首先生成一个irqaction结构,其次根据中断号找到irq_desc数组项(还记得吧,内核中irq_desc是一个数组,每一项对应一个中断号),然后将irqaction结构添加到irq_desc中的action链表中。当然还做一些其他的工作,注册完成后,中断函数就可以发生并被处理了。

>>中断的处理流程     这个流程主要是产生中断后调用irq_chip中的函数来屏蔽,清除中断等等,然后调用irqaction结构中用户注册的中断函数处理中断,当然还有很多其他的事情要做,不过主要流程是这样。

此外要注意的是,linux中断设计为高层中断处理(highlevel irq-events handler)和底层中断处理(lowlevel irq-events handler)。高层主要为中断linux流程处理,底层与硬件密切相关,如desc->handle_irq为linux高层流程处理,而desc->action为底层硬件处理从而需要用户实现,又如中断处理完成后chip->eoi为底层流程结束时处理函数(主要操作芯片寄存器),而chip->end为高层流程中断结束时处理函数。

三.中断注册流程

>>request_irq()

include/linux/interrupt.h

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

}

里面就一个request_threaded_irq函数,参数的话irq为中断号,handler为用户的中断函数,flags中断标识,name中断名称,dev可以是传递的参数可以是作为区分共享中断,对应irqaction结构中的dev_id。共享中断的dev_id值必须唯一。其实request_irq主要是把这些参数来构造一个irqaction结构添加到链表中。

>>request_threaded_irq()

kernel/irq/manage.c

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;

 

    retval = __setup_irq(irq, desc, action);

    if (retval)

        kfree(action);

主要利用用户传递的参数构造一个irqaction结构,并调用__setup_irq函数。

>>__setup_irq()

kernel/irq/manage.c

__setup_irq内容比较多,主要完成几个方面的工作

>>>添加irqaction结构到irq_desc的action链表中,需要判断是否为共享中断,只有共享中断可以添加多个中断处理函数。如果是共享中断,则要检查中断处理函数是否和链表中其他函数的触发方式等是否相同,只有一致才可以添加到链表中。

>>>设置一些irq_chip结构中的函数指针指向默认函数

>>>设置中断的触发方式和启动中断

完成request_irq后中断就可以被接收和处理了。

举例:设置GPIO口为中断输入的流程

request_irq() --> request_threaded_irq -->__setup_irq() --> __irq_set_trigger() (定义在kernel/irq/manage.c里) -->set_type()

set_type()为板卡初始化时设置的函数(MACHINE_START)。

四.中断处理流程

中断处理流程又是一个比较复杂的过程,要牵涉到ARM的工作模式,异常,异常向量,还有一堆汇编代码。

首先异常向量表,保存一条跳转指令,一般存放在0x00000000或者0xffff000地址,linux使用后者,中断发生后CPU进入异常模式,将跳转到相应的异常向量表处执行,异常向量表保存跳转指令,经过一段汇编,最后跳转到中断的入口asm_do_IRQ。

arch/arm/kernel/irq.c

/*

 * do_IRQ handles all hardware IRQ's.  Decoded IRQs should not

 * come via this function.  Instead, they should provide their

 * own 'handler'

 */

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 (irq >= NR_IRQS)

        handle_bad_irq(irq, &bad_irq_desc);

    else

        generic_handle_irq(irq);

 

    /* AT91 specific workaround */

    irq_finish(irq);

 

    irq_exit();

    set_irq_regs(old_regs);

}
generic_handle_irq()

include/linux/irq.h

/*

 * Architectures call this to let the generic IRQ layer

 * handle an interrupt. If the descriptor is attached to an

 * irqchip-style controller then we call the ->handle_irq() handler,

 * and it calls __do_IRQ() if it's attached to an irqtype-style controller.

 */

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

}
   

static inline void generic_handle_irq(unsigned int irq)

{

    generic_handle_irq_desc(irq, irq_to_desc(irq));

}

desc->handle_irq()无论是板卡初始化时MACHINE_START还是request_irq()申请中断时,都需要注册desc的handle_irq。其根据中断模式可选择handle_edge_irq()或handle_level_irq()。这里调用desc->handle_irq分为两种情况,一是单独的中断号的,一是共享中断号的,俩者的区别在于后者需要先判断是共享中断中的哪一个然后再真正的去调用handle_irq,所以我这里分析一下单独中断号的处理流程,共享中断也是一样可以分析。

kernel/irq/chip.c

/**

 *  handle_edge_irq - edge type IRQ handler

 *  @irq:   the interrupt number

 *  @desc:  the interrupt description structure for this irq

 *

 *  Interrupt occures on the falling and/or rising edge of a hardware

 *  signal. The occurence is latched into the irq controller hardware

 *  and must be acked in order to be reenabled. After the ack another

 *  interrupt can happen on the same source even before the first one

 *  is handled by the assosiacted event handler. If this happens it

 *  might be necessary to disable (mask) the interrupt depending on the

 *  controller hardware. This requires to reenable the interrupt inside

 *  of the loop which handles the interrupts which have arrived while

 *  the handler was running. If all pending interrupts are handled, the

 *  loop is left.

 */

void

handle_edge_irq(unsigned int irq, struct irq_desc *desc)

{

    spin_lock(&desc->lock);

 

    desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

 

    /*

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

     * we shouldn't process the IRQ. Mark it pending, handle

     * the necessary masking and go out

     */

    if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||

            !desc->action)) {

        desc->status |= (IRQ_PENDING | IRQ_MASKED);

        mask_ack_irq(desc, irq);       //屏蔽并清除中断

        desc = irq_remap_to_desc(irq, desc);

        goto out_unlock;

    }

    kstat_incr_irqs_this_cpu(irq, desc);   //中断统计计数

 

    /* Start handling the irq */

    if (desc->chip->ack)

        desc->chip->ack(irq);      //应答中断

    desc = irq_remap_to_desc(irq, desc);

 

    /* Mark the IRQ currently in progress.*/

    desc->status |= IRQ_INPROGRESS;  //标记中断状态

 

    do {

        struct irqaction *action = desc->action;

        irqreturn_t action_ret;

 

        if (unlikely(!action)) {

            desc->chip->mask(irq);

            goto out_unlock;

        }

 

        /*

         * When another irq arrived while we were handling

         * one, we could have masked the irq.

         * Renable it, if it was not disabled in meantime.

         */

        if (unlikely((desc->status &

                   (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==

                  (IRQ_PENDING | IRQ_MASKED))) {

            desc->chip->unmask(irq);

            desc->status &= ~IRQ_MASKED;

        }

 

        desc->status &= ~IRQ_PENDING;

        spin_unlock(&desc->lock);

        action_ret = handle_IRQ_event(irq, action);   // 处理中断,最重要的函数,注意参数,action这个参                                                                                        //数将联系到我们的用户中断处理函数

        if (!noirqdebug)

            note_interrupt(irq, desc, action_ret);

        spin_lock(&desc->lock);

 

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

 

    desc->status &= ~IRQ_INPROGRESS;

out_unlock:

    spin_unlock(&desc->lock);

}

handle_IRQ_event()调用了action中的handler,我们注册中断的时候主要任务就是构建一个irqaction结构并添加到irq_desc中的irqaction链表中的指针action下面。至此系统才调用我们注册的中断处理函数处理中断。

kernel/irq/handle.c

/**

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

 

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

        local_irq_enable_in_hardirq();

 

    do {

        trace_irq_handler_entry(irq, action);

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

        trace_irq_handler_exit(irq, action, ret);

 

        switch (ret) {

        case IRQ_WAKE_THREAD:

            /*

             * Set result to handled so the spurious check

             * does not trigger.

             */

            ret = IRQ_HANDLED;

            /*

             * Catch drivers which return WAKE_THREAD but

             * did not set up a thread function

             */

            if (unlikely(!action->thread_fn)) {

                warn_no_thread(irq, action);

                break;

            }

 

            /*

             * Wake up the handler thread for this

             * action. In case the thread crashed and was

             * killed we just pretend that we handled the

             * interrupt. The hardirq handler above has

             * disabled the device interrupt, so no irq

             * storm is lurking.

             */

            if (likely(!test_bit(IRQTF_DIED,

                         &action->thread_flags))) {

                set_bit(IRQTF_RUNTHREAD, &action->thread_flags);

                wake_up_process(action->thread);

            }

 

            /* Fall through to add to randomness */

        case IRQ_HANDLED:

            status |= action->flags;

            break;

 

        default:

            break;

        }

 

        retval |= ret;

        action = action->next;

    } while (action);

 

    if (status & IRQF_SAMPLE_RANDOM)

        add_interrupt_randomness(irq);

    local_irq_disable();

 

    return retval;

}
handle_IRQ_event

至此中断处理流程结束。

五.共享中断

《Linux Kernel Development – Second Edition》第六章中Shared Handlers这一节,其中有段总结性的文字如下:

When the kernel receives an interrupt, it invokes sequentially each registered handler on the line. Therefore, it is important that the handler be capable of distinguishing whether it generated a given interrupt. The handler must quickly exit if its associated device did not generate the interrupt. This requires the hardware device to have a status register (or similar mechanism) that the handler can check. Most hardware does indeed have such a feature.

这段话的大概意思是,发生中断时,内核并不判断究竟是共享中断线上的哪个设备产生了中断,它会循环执行所有该中断线上注册的中断处理函数(即irqaction->handler函数)。因此irqaction->handler函数有责任识别出是否是自己的硬件设备产生了中断,然后再执行该中断处理函数。通常是通过读取该硬件设备提供的中断状态标志位进行判断。

 

实际上dev_id作用主要有两点:
>>在中断处理程序释放时使用到dev_id:
When your driver unloads, you need to unregister your interrupt handler and potentially disable the interrupt line. To do this, call
void free_irq(unsigned int irq, void *dev_id)
……
The fifth parameter, dev_id, is used primarily for shared interrupt lines. When an interrupt handler is freed (discussed later), dev_id provides a unique cookie to allow the removal of only the desired interrupt handler from the interrupt line. Without this parameter, it would be impossible for the kernel to know which handler to remove on a given interrupt line.

这里《LKD2》讲了很清楚,当调用free_irq注销中断处理函数时(通常卸载驱动时其中断处理函数也会被注销掉),因为dev_id是唯一的,所以可以通过它来判断从共享中断线上的多个中断处理程序中删除指定的一个。如果没有这个参数,那么kernel不可能知道给定的中断线上到底要删除哪一个处理程序。

 

>>将使用该中断处理程序的设备结构体传递给该中断处理程序:
首先看两个基础条件:

1) 内核中的各个设备结构体肯定是唯一的,因此满足dev_id唯一这个要求

2) dev_id参数会在发生中断时传递给该中断的服务程序。

典型的中断服务程序定义如下:

static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs);

即request_irq的dev_id参数会传递给该中断服务程序的dev_id。因此也可以将驱动程序的设备结构体通过dev_id传递给中断服务程序

这样做作用主要有两个:

1) 中断服务程序可能使用到设备结构体中的信息。通过将设备结构传递给request_irq的dev_id参数这种机制,在中断处理函数中使用该设备结构体,这是大部分驱动程序通用的一种手法。

2)前面我们讲了若使用共享中断,那么中断处理函数自身需要能识别是否是自己的设备产生了中断。通常这是通过读取该硬件设备提供的中断flag标志位进行判断的。

而往往驱动程序里定义的设备结构体通常包含了该设备的IO地址,加上偏移就可以算出中断状态寄存器及中断flag标志位的IO地址信息。因此将设备结构体通过dev_id传递给设备中断处理程序的另一个作用就是使用共享中断时,可以在中断处理函数内通过读取该设备结构 (dev_id) 中提供的中断Flag标志位地址信息进行判断,是否是该设备产生了中断,然后再进一步判断是否继续往下执行还是跳到下一个irqaction->handler函数再判断执行。当然,如果本来就知道该设备中断状态寄存器的IO地址,也可以选择直接readl该地址信息进行判断。

 

参考:

1. http://blog.csdn.net/yimu13/article/details/6803957 linux中断流程详解

2. http://blog.chinaunix.net/uid-15193587-id-2774837.html 深入分析request_irq的dev_id参数作用 

posted @ 2016-09-20 14:53  yuxi_o  阅读(2097)  评论(0编辑  收藏  举报