中断申请函数request_irq详解

来源:互联网 发布:高分一号数据波段运算 编辑:程序博客网 时间:2024/05/18 02:00

原地址:http://blog.csdn.net/wealoong/article/details/7566546

一、中断注册方法

在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义:

int request_irq(unsigned int irq, irq_handler_t handler,
                         unsigned long irqflags, const char *devname, void *dev_id)

irq是要申请的硬件中断号

handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。

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

devname设置中断名称,通常是设备驱动程序的名称  在cat /proc/interrupts中可以看到此名称。

dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。

request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

==================================================================================================================================

request_irq() | 注册中断服务

在 2.4 内核和 2.6内核中都使用 request_irq() 函数来注册中断服务函数。在 2.4 内核中,需要包含的头文件是 #include <linux/sched.h> ,2.6 内核中需要包含的头文件则是
#include <linux/interrupt.h> 。函数原型如下:

  • 2.4 内核
int request_irq (unsignedintirq,void (*handler)(int,void*,structpt_regs*),unsignedlongfrags,constchar*device,void*dev_id);

  • 2.6 内核
request_irq(unsignedintirq,irq_handler_thandler,unsignedlongflags,constchar*name,void*dev);

参数说明

在发生对应于第 1个参数 irq 的中断时,则调用第 2 个参数handler 为要注册的中断服务函数(也就是把 handler() 中断服务函数注册到内核中 )。

第 3 个参数 flags 指定了快速中断或中断共享等中断处理属性。在 2.6 教新的内核里(我的是 2.6.27 ~ 2.6.31 ),在 linux/interrupt.h 中定义操作这个参数的宏如下:
引用
/*
 * 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_NONE0x00000000
#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.
*                registered first in an shared interrupt is considered for
*                performance reasons)
*/
#define IRQF_DISABLED           0x00000020        * IRQF_DISABLED - keep irqs disabled when calling the action handler
#define IRQF_SAMPLE_RANDOM      0x00000040* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
#define IRQF_SHARED             0x00000080* IRQF_SHARED - allow sharing the irq among several devices
#define IRQF_PROBE_SHARED       0x00000100* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
#define IRQF_TIMER              0x00000200* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
#define IRQF_PERCPU             0x00000400* IRQF_PERCPU - Interrupt is per cpu
#define IRQF_NOBALANCING        0x00000800* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
#define IRQF_IRQPOLL            0x00001000* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
早期一点的 2.6 内核这里一般以 SA_ 前缀开头,如:
SA_INTERRUPT   表示禁止其他中断;(对应于 IRQF_DISABLED )
SA_SHIRQ             表示共享相同的中断号 (对应于 IRQF_SHARED )
SA_SAMPLE_RANDOM   此宏会影响到 RANDOM 的处理( 对应于 IRQF_SAMPLE_RANDOM )。

第 4 个参数 name,通常是设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟) 文件上,或内核发生中断错误时使用。

第 5 个参数 dev_id 中断名称 可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需要参考的数据地址。建议将设备结构指针作为dev_id参数

int request_irq(unsigned int irq, irq_handler_t handler,
        
IRQF_SHARED, const char *devname, void *dev_id)

很多权威资料中都提到,中断共享注册时的注册函数中的dev_id参数是必不可少的,并且dev_id的值必须唯一。那么这里提供唯一的dev_id值的究竟是做什么用的?

根据我们前面中断模型的知识,可以看出发生中断时,内核并不判断究竟是共享中断线上的哪个设备产生了中断,它会循环执行所有该中断线上注册的中断处理函数(即irqaction->handler函数)。因此irqaction->handler函数有责任识别出是否是自己的硬件设备产生了中断,然后再执行该中断处理函数。通常是通过读取该硬件设备提供的中断flag标志位进行判断。那既然kernel循环执行该中断线上注册的所有irqaction->handler函数,把识别究竟是哪个硬件设备产生了中断这件事交给中断处理函数本身去做,那request_irq的dev_id参数究竟是做什么用的?

很多资料中都建议将设备结构指针作为dev_id参数。在中断到来时,迅速地根据硬件寄存器中的信息比照传入的dev_id参数判断是否是本设备的中断,若不是,应迅速返回。这样的说法没有问题,也是我们编程时都遵循的方法。但事实上并不能够说明为什么中断共享必须要设置dev_id。

下面解释一下dev_id参数为什么必须的,而且是必须唯一的。

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


注销函数定义在Kernel/irq/manage.c中定义: 
    
void free_irq(unsigned int irq, void *dev_id)


返回值
函数运行正常时返回 0 ,否则返回对应错误的负值。

示例代码片段
引用

irqreturn_t xxx_interrupt (intirq,void*dev_id)
{
        ...

        return (IRQ_HANDLED);
}

int xxx_open (struct inode *inode,structfile*filp)
{
        if (!request_irq (XXX_IRQ,xxx_interruppt,IRQF_DISABLED,"xxx",NULL)){

                /*正常注册*/
        }

        return (0);
}

============================================================================

内核中的中断处理模型

内核版本: Linux 2.6.19

Kernel中断处理模型结构图如下:

内核中的中断处理模型

下面简单介绍一下:

1. Linux定义了名字为irq_desc的中断例程描述符表:(include/linux/irq.h)

struct irqdesc irq_desc[NR_IRQS];

NR_IRQS表示中断源的数目。

2. irq_desc[]是一个指向irq_desc结构的数组, irq_desc结构是各个设备中断服务例程的描述符。

struct irq_desc {
irq_flow_handler_t handle_irq;
struct irq_chip *chip;
void *handler_data;
void *chip_data;
struct irqaction *action;
unsigned int status;

unsigned intdepth;
unsigned int wake_depth;
unsigned int irq_count;
unsigned int irqs_unhandled;
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_t affinity;
unsigned int cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry*dir;
#endif
const char *name;
} ____cacheline_aligned;

Irq_desc结构体中的成员action指向该中断号对应的irqaction结构体链表。Irqaction结构体定义如下:

// include/linux/interrupt.h
struct irqaction{
irq_handler_t handler; // 指向中断服务程序 
unsigned long flags; // 中断标志
unsigned long mask; // 中断掩码
const char *name;// I/O设备名

void *dev_id;// 设备标识
struct irqaction*next;// 指向下一个描述符

int irq;// IRQ线
struct proc_dir_entry *dir// 指向IRQn相关的/proc/irq/n目录的描述符
};

其中关键的handler成员指向了该设备的中断服务程序,由执行request_irq时建立。

3. 在驱动程序初始化时,若使用到中断,通常调用函数request_irq()建立该驱动程序对应的irqaction结构体,并把它登记到irq_desc [irq_num]->action链表中。Iqr_num为驱动程序申请的中断号。

request_irq()函数的原型如下:

// kernel/irq/manage.c
int request_irq(unsignedint irq,
irqreturn_t (*handler)(int,void*,struct pt_regs*),
unsignedlong irqflags,
const char *devname,
void *dev_id);

参数irq是设备中断求号,在向irq_desc []数组登记时,它做为数组的下标。把中断号为irq的irqaction结构体的首地址写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起了。

这样当CPU接收到中断请求后,就可以根据中断号通过irq_desc []找到该设备的中断服务程序。流程如上图所示。

4. 关于共享中断

共享中断的不同设备的iqraction结构体都会添加进该中断号对应的irq_desc结构体的action成员所指向的irqaction链表内。当内核发生中断时,它会依次调用该链表内所有的handler函数。因此,若驱动程序需要使用共享中断机制,其中断处理函数必须有能力识别是否是自己的硬件产生了中断。通常是通过读取该硬件设备提供的中断flag标志位进行判断。


原文地址:http://blog.chinaunix.net/uid-103601-id-2961346.html

Request_irq的作用是申请使用IRQ并注册中断处理程序。

request_irq()函数的原型如下:

/* kernel/irq/manage.c */
int request_irq(unsigned int irq,
    irqreturn_t (*handler)(int, void *, struct pt_regs *),
    unsigned long irqflags, 
    const char *devname, 
    void *dev_id);

 

我们知道,当使用内核共享中断时,request_irq必须要提供dev_id参数,并且dev_id的值必须唯一。那么这里提供唯一的dev_id值的究竟是做什么用的?

 

起先我以为dev_id的值是提供给kernel进行判断共享中断线上的哪一个设备产生了中断(即哪个irqaction产生中断),然后执行相应的中断处理函数(irqaction->handler)。实际上不是的,我们来看看《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函数有责任识别出是否是自己的硬件设备产生了中断,然后再执行该中断处理函数。通常是通过读取该硬件设备提供的中断flag标志位进行判断。

 

那既然kernel循环执行该中断线上注册的所有irqaction->handler函数,把识别究竟是哪个硬件设备产生了中断这件事交给中断处理函数本身去做,那request_irqdev_id参数究竟是做什么用的?

 

我总结了一下,实际上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不可能知道给定的中断线上到底要删除哪一个处理程序。

 

下面我们来看一下free_irq的代码:

void free_irq(unsigned int irq, void *dev_id)
{
    struct irq_desc *desc;
    struct irqaction **p;
    unsigned long flags;

    WARN_ON(in_interrupt());
    if (irq >= NR_IRQS)
        return;

    desc = irq_desc + irq;    /* 获取该中断号对应的irq_desc */
    spin_lock_irqsave(&desc->lock, flags);
    p = &desc->action;        /* 找到该irq的irqaction链表 */
    for (;;) {
        struct irqaction *action = *p;

        if (action) {
            struct irqaction **pp = p;

            p = &action->next;
            if (action->dev_id != dev_id) 
                continue; /* 这两句进行循环判断,直到找到相应dev_id设备的irqaction */

            /* Found it - now remove it from the list of entries */
            *pp = action->next; /* 指向下一个irqaction */

            /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
            if (desc->chip->release)
                desc->chip->release(irq, dev_id);
#endif

            if (!desc->action) {
                desc->status |= IRQ_DISABLED;
                if (desc->chip->shutdown)
                    desc->chip->shutdown(irq);
                else
                    desc->chip->disable(irq);
            }
            recalculate_desc_flags(desc);
            spin_unlock_irqrestore(&desc->lock, flags);
            unregister_handler_proc(irq, action);

            /* Make sure it's not being used on another CPU */
            synchronize_irq(irq);
            kfree(action);        /* 删除该设备(dev_id)的irqaction */
            return;
        }
        printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq);
        spin_unlock_irqrestore(&desc->lock, flags);
        return;
    }
}

 

二.将使用该中断处理程序的设备结构体传递给该中断处理程序

首先看两个基础条件:

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

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

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

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

 

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

这样做作用主要有两个:

1) 中断服务程序可能使用到设备结构体中的信息

s3c2410的iis音频驱动,它是将DMA Channel结构体通过dev_id传递给中断服务程序

/* arch/arm/mach-s3c2410/dma.c */
int s3c2410_dma_request(unsigned int channel, struct s3c2410_dma_client *client,void *dev)
{
err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED,
                 client->name, (void *)chan
);
……
}

 

目的是中断处理函数s3c2410_dma_irq内需要用到chan结构体的duf成员和load_state成员等。

使用方法如下:

/* arch/arm/mach-s3c2410/dma.c */
static irqreturn_t
s3c2410_dma_irq(int irq, void *devpw, struct pt_regs *regs)
{
    struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw; /* 这里强制转化成request_irq时传给dev_id的设备类型,此处即s3c2410_dma_chan ,下面便可以使用它*/
    struct s3c2410_dma_buf *buf;
    buf = chan->curr;
    ……
}

 

因此,我们可以通过将设备结构传递给request_irq的dev_id参数这种机制,在中断处理函数中使用该设备结构体,这是大部分驱动程序通用的一种手法。

 

2)前面我们讲了若使用共享中断,那么中断处理函数自身需要能识别是否是自己的设备产

生了中断。通常这是通过读取该硬件设备提供的中断flag标志位进行判断的。

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


0 0