Linux驱动设计—— 中断与时钟@request_irq参数详解

时间:2023-05-11 23:42:14

request_irq函数定义

/*include <linux/interrupt.h>*/
int request_irq(unsigned int irq,
          irq_handler_t handler,
          unsigned long irqflags,
          const char *devname,
          void *dev_id)

使用:

将中断号irq与中断处理函数handler对应

返回值:成功返回0,失败返回非0。

参数:

irq:指定要分配的中断号,中断号的定义在“include/mach/irqs.h”中。注意,不管是单独占有中断请求线的中断,还是共享中断请求线的每个中断,都有一个对应的中断号。,所以,调用该函数不需要考虑是哪种中断(是否共享寄存器),你想哪种中断响应,你就填对应的中断号。

handler:中断处理函数指针。

  2.6.30.4及以后的内核中断处理函数的声明为  

//2.6.30.4及以后内核
irqreturn_t handler(int irq, void *dev_id)
//老版本内核声明
irqreturn_t handler(int irq, void *dev_id, struct pt_regs *regs)

  返回值

enum irqreturn {
IRQ_NONE,
IRQ_HANDLED,
IRQ_WAKE_THREAD,
};

  

irqflags:中断处理标记

/*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_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 /*每CPU周期执行中断*/
#define IRQF_NOBALANCING 0x00000800 /*复位中断*/
#define IRQF_IRQPOLL 0x00001000 /*共享中断中根据注册时间判断*/

  

/*linux/irq.h*/
/*
* IRQ line status.
*
* Bits 0-7 are reserved for the IRQF_* bits in linux/interrupt.h
*
* IRQ types
*/
#define IRQ_TYPE_NONE 0x00000000 /* Default, unspecified type */
#define IRQ_TYPE_EDGE_RISING 0x00000001 /* Edge rising type */   //上升沿触发
#define IRQ_TYPE_EDGE_FALLING 0x00000002 /* Edge falling type */ //下降沿触发
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING) //边沿触发
#define IRQ_TYPE_LEVEL_HIGH 0x00000004 /* Level high type */    //高电平触发
#define IRQ_TYPE_LEVEL_LOW 0x00000008 /* Level low type */     //低电平触发
#define IRQ_TYPE_SENSE_MASK 0x0000000f /* Mask of the above */  //
#define IRQ_TYPE_PROBE 0x00000010 /* Probing in progress */     // /* Internal flags */
#define IRQ_INPROGRESS 0x00000100 /* IRQ handler active - do not enter! */  //
#define IRQ_DISABLED 0x00000200 /* IRQ disabled - do not enter! */       //中断禁止
#define IRQ_PENDING 0x00000400 /* IRQ pending - replay on enable */      //
#define IRQ_REPLAY 0x00000800 /* IRQ has been replayed but not acked yet */  //
#define IRQ_AUTODETECT 0x00001000 /* IRQ is being autodetected */      //
#define IRQ_WAITING 0x00002000 /* IRQ not yet seen - for autodetection */  //
#define IRQ_LEVEL 0x00004000 /* IRQ level triggered */              //
#define IRQ_MASKED 0x00008000 /* IRQ masked - shouldn't be seen again */ //
#define IRQ_PER_CPU 0x00010000 /* IRQ is per CPU */               //
#define IRQ_NOPROBE 0x00020000 /* IRQ is not valid for probing */        //
#define IRQ_NOREQUEST 0x00040000 /* IRQ cannot be requested */        //
#define IRQ_NOAUTOEN 0x00080000 /* IRQ will not be enabled on request irq */
#define IRQ_WAKEUP 0x00100000 /* IRQ triggers system wakeup */
#define IRQ_MOVE_PENDING 0x00200000 /* need to re-target IRQ destination */
#define IRQ_NO_BALANCING 0x00400000 /* IRQ is excluded from balancing */
#define IRQ_SPURIOUS_DISABLED 0x00800000 /* IRQ was disabled by the spurious trap */
#define IRQ_MOVE_PCNTXT 0x01000000 /* IRQ migration from process context */
#define IRQ_AFFINITY_SET 0x02000000 /* IRQ affinity was set from userspace*/ #define IRQ_SUSPENDED 0x04000000 /* IRQ has gone through suspend sequence */

    

devname:该字符串将显示在/proc/irq和/proc/interrupts中。

  为占用中断的设备命名,只是一个字符串,用于在系统中查看哪些中断被占用及占用的设备名称。

  # cat /proc/interrupts

dev_id:ID

  当使用内核共享中断时,request_irq必须要提供dev_id参数,并且dev_id的值必须唯一。这个参数与request_irq()的参数dev_id一致。

转自:http://blog.chinaunix.net/uid-20696246-id-1891946.html

《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_irq的dev_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_irq的dev_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该地址信息进行判断。

2.6.26内核ARM中断实现(转)

四、中断处理模型

要想弄清楚desc->handle_irq(irq, desc)和我们注册的中断有什么关联,就要了解中断处理模型了。

4.1 中断处理模型结构

中断处理模型如下图所示,

其中NR_IRQS表示最大的中断号,在include/asm/arch/irq.h中定义。

irq_desc[]是一个指向irq_desc_t结构的数组, irq_desc_t结构是各个设备中断服务例程的描述符。Irq_desc_t结构体中的成员action指向该中断号对应的irqaction结构体链表。Irqaction结构体定义在include/linux/interrupt.h中,如下:

truct irqaction {
irq_handler_t handler; //中断处理函数,注册时提供
unsigned long flags; //中断标志,注册时提供
cpumask_t mask; //中断掩码
const char *name; //中断名称
void *dev_id; //设备id,本文后面部分介绍中断共享时会详细说明这个参数的作用
struct irqaction *next; //如果有中断共享,则继续执行,
int irq; //中断号,注册时提供
struct proc_dir_entry *dir; //指向IRQn相关的/proc/irq/n目录的描述符
};

在注册中断号为irq的中断服务程序时,系统会根据注册参数封装相应的irqaction结构体。并把中断号为irq的irqaction结构体写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起了。样当CPU接收到中断请求后,就可以根据中断号通过irq_desc []找到该设备的中断服务程序。

4.2 中断共享的处理模型

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

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)

五、S3C2410子中断的注册的实现

5.1 S3C2410子中断注册问题的提出

参看3.5节中判断中断号的方法,可以看到只是通过S3C2410中断控制器中的INTOFFSET寄存器来判断的。对于INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC 等带有子中断的向量,INTOFFSET无法判断出具体的中断号。平台留给我们的注册方法如下:

在include/asm/arch/irqs.h中有类似如下定义:

/* interrupts generated from the external interrupts sources */
#define IRQ_EINT4 S3C2410_IRQ(32) /* 48 */
#define IRQ_EINT5 S3C2410_IRQ(33)
#define IRQ_EINT6 S3C2410_IRQ(34)
#define IRQ_EINT7 S3C2410_IRQ(35)
#define IRQ_EINT8 S3C2410_IRQ(36)
#define IRQ_EINT9 S3C2410_IRQ(37)
#define IRQ_EINT10 S3C2410_IRQ(38)
#define IRQ_EINT11 S3C2410_IRQ(39)
#define IRQ_EINT12 S3C2410_IRQ(40)
#define IRQ_EINT13 S3C2410_IRQ(41)
#define IRQ_EINT14 S3C2410_IRQ(42)
#define IRQ_EINT15 S3C2410_IRQ(43)
#define IRQ_EINT16 S3C2410_IRQ(44)
#define IRQ_EINT17 S3C2410_IRQ(45)
#define IRQ_EINT18 S3C2410_IRQ(46)
#define IRQ_EINT19 S3C2410_IRQ(47)
#define IRQ_EINT20 S3C2410_IRQ(48) /* 64 */
#define IRQ_EINT21 S3C2410_IRQ(49)
#define IRQ_EINT22 S3C2410_IRQ(50)
#define IRQ_EINT23 S3C2410_IRQ(51)

可以看到平台为每种子中断都定义了中断号,如果你想实现EINT10的中断注册,直接按照IRQ_EINT10这个中断号注册都可以了。那么平台代码是如何实现这部分中断注册的呢?

5.2 S3C2410子中断注册问题的解决

/*arch/arm/plat-s3c24xx/irq.c*/
void __init s3c24xx_init_irq(void)
{……
  set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
  set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
  set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
  set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
  set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
  set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

……
}

平台在初始化时会调用到s3c24xx_init_irq,在此函数中实现了对EINT4_7、EINT8_23、INT_UART0、INT_ADC等中断的注册。下面看看这些带有子中断的中断号对应的处理函数的内容。以IRQ_EINT4t7为例,其它情况类似。

/*arch/arm/plat-s3c24xx/irq.c*/
s3c_irq_demux_extint4t7(unsigned int irq,
struct irq_desc *desc)
{
unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
eintpnd &= ~eintmsk;
eintpnd &= 0xff; /* only lower irqs */

/* eintpnd中可以有多个位同时置1,这一点和intpnd的只能有1个位置1是不一样的 */
while (eintpnd) { //循环执行所有置位的子中断
irq = __ffs(eintpnd); //算出第一个不为0的位,类似arm v5后的clz前导0的作用
eintpnd &= ~(1<<irq);//清除相应的位
irq += (IRQ_EINT4 - 4);//算出对应的中断号
desc_handle_irq(irq, irq_desc + irq);//执行对应子中断的注册函数
}
}

从上面的函数可以看出子中断是如何注册及被调用到的。有人可能会问为何不在include/asm/arch-s3c2410/entry-macro.s 文件中get_irqnr_and_base函数判断中断号时,直接算出对应的子中断号,就可以直接找到子中断处理了呢?

原因是: get_irqnr_and_base是平台给系统提供的函数,对于多个子中断同时置位的情况无法通过一个值返回(因为子中断中,如eintpnd是可以多个位同时置位的))。而intpnd则没有这个问题。