一、中断基础概念
所谓中断,指CPU在执行程序的过程中,出现了某些突发事件即待处理,CPU必须暂停当前的程序。转去处理突发事件,处理完毕后CPU又返回原程序被中断的位置并继续执行。
1、中断分类
a -- 内部中断和外部中断
根据中断的的来源,中断可以分为内部中断和外部中断:
内部中断,其中断源来自CPU内部(软件中断指令、溢出、除法错误等),例如,操作系统从用户态切换到内核态需借助CPU内部的软中断;
外部中断,其中断源来自CPU外部,由外设提出请求;
b -- 可屏蔽中断与不屏蔽中断
根据中断是否可以屏蔽分为可屏蔽中断与不屏蔽中断:
可屏蔽中断,其可以通过屏蔽字被屏蔽,屏蔽后,该中断不再得到响应;
不屏蔽中断,其不能被屏蔽;
c -- 向量中断和非向量中断
根据中断入口跳转方法的不同,分为向量中断和非向量中断:
向量中断,采用向量中断的CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同的中断号有不同的入口地址;
非向量中断,其多个中断共享一个入口地址,进入该入口地址后再通过软件判断中断标志来标识具体是哪个中断。
也就是说,向量中断由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务入口地址。
2、中断ID
a -- IRQ number
cpu给中断的一个编号,一个IRQ number是一个虚拟的interrupt ID,和硬件无关;
b -- HW interrupt ID
对于中断控制器而言,它收集了多个外设的irq request line,要向cpu传递,GIC要对外设进行编码,GIC就用HW interrupt ID来标示外部中断;
3、SMP情况下中断两种形态
1-Nmode :只有一个processor处理器
N-N :所有的processor都是独立收到中断的
GIC:SPI使用1-Nmode PPI 和 sgi使用N-Nmode
二、中断编程
1、 申请IRQ
在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)
相关参数:
a -- irq是要申请的硬件中断号。另外,这里要思考的问题是,这个irq 是怎么得到的?这里我们在设备树中获取,具体解析见:
b -- handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。
c -- irqflags是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)
d -- devname设置中断名称,在cat /proc/interrupts中可以看到此名称。
e -- dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。
顶半部 handler 的类型 irq_handler_t 定义为:
typedef irqreturn_t (*irq_handler_t)(int, void *);
参数1:中断号
参数2 :参数
在Interrupt.h (e:\linux-3.14-fs4412\include\linux) 18323 2014/3/31中定义
IRQ_NONE 共享中断,如果不是我的设备产生的中断,就返回该值
IRQ_HANDLED 中断处理函数正确执行了就返回该值
IRQ_WAKE_THREAD = (1 << 1)
2、释放IRQ
与request_irq()相对应的函数为 free_irq(),free_irq()的原型为:
void free_irq(unsigned int irq, void *dev_id)
free_irq()参数的定义与request_irq()相同。
三、中断注册过程分析
我们每次用中断的时候就是注册一个中断函数。request_irq首先生成一个irqaction结构,其次根据中断号 找到irq_desc数组项(还记得吧,内核中irq_desc是一个数组,没一项对应一个中断号),然后将irqaction结构添加到 irq_desc中的action链表中。当然还做一些其他的工作,注册完成后,中断函数就可以发生并被处理了。
irq_desc 内核中记录一个irq_desc的数组,数组的每一项对应一个中断或者一组中断使用同一个中断号,一句话irq_desc几乎记录所有中断相关的东西,这个结构是中断的核心。其中包括俩个重要的结构irq_chip 和irqaction 。
1、 irq_chip
irq_chip 里面基本上是一些回调函数,其中大多用于操作底层硬件,设置寄存器,其中包括设置GPIO为中断输入就是其中的一个回调函数,分析一些源代码
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); 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); //设置中断类型,其中包括设置GPIO口为中断输入 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; };
我们可以看到这里实现的是一个框架,需要我们进一步的填充里面的函数。我们在分析另一个结构irqaction
2、irqaction
include/linux/interrupt.h struct irqaction { irq_handler_t handler; //用户注册的中断处理函数 unsigned long flags; //中断标识 const char *name; //用户注册的中断名字,cat/proc/interrupts时可以看到 void *dev_id; //可以是用户传递的参数或者用来区分共享中断 struct irqaction *next; //irqaction结构链,一个共享中断可以有多个中断处理函数 int irq; //中断号 struct proc_dir_entry *dir; irq_handler_t thread_fn; struct task_struct *thread; unsigned long thread_flags; };
我们用irq_request函数注册中断时,主要做俩个事情,根据中断号生成一个irqaction结构并添加到irq_desc中的 action结构链表,另一发面做一些初始化的工作,其中包括设置中断触发方式,设置一些irq_chip结构中没有初始化的函数为默认,开启中断,设置 GPIO口为中断输入模式(这里后面有详细流程分析)。
四、实例分析
下面是一个按键中断驱动的编写,具体硬件分析在:Exynos4412 中断驱动开发(三)—— 设备树中中断节点的创建
1、driver.c
#include <linux/module.h> #include <linux/device.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/fs.h> #include <linux/wait.h> #include <linux/sched.h> #include <asm/uaccess.h> static int major = 250; static wait_queue_head_t wq; static int have_data = 0; static int key; static struct resource *res1; static struct resource *res2; static irqreturn_t key_handler(int irqno, void *dev) { // printk("key_handler irqno =%d \n",irqno); if(irqno == res1->start) { key = 1; } if(irqno == res2->start) { key = 2; } have_data = 1; wake_up_interruptible(&wq); return IRQ_HANDLED; } static int key_open (struct inode *inod, struct file *filep) { return 0; } static ssize_t key_read(struct file *filep, char __user *buf, size_t len, loff_t *pos) { wait_event_interruptible(wq, have_data==1); if(copy_to_user(buf,&key,sizeof(int))) { return -EFAULT; } have_data = 0; return len; } static int key_release(struct inode *inode, struct file *filep) { return 0; } static struct file_operations key_ops = { .open = key_open, .release = key_release, .read = key_read, }; static int hello_probe(struct platform_device *pdev) { int ret; printk("match 0k \n"); res1 = platform_get_resource(pdev,IORESOURCE_IRQ, 0); res2 = platform_get_resource(pdev,IORESOURCE_IRQ, 1); ret = request_irq(res1->start,key_handler,IRQF_TRIGGER_FALLING|IRQF_DISABLED,"key1",NULL); ret = request_irq(res2->start,key_handler,IRQF_TRIGGER_FALLING|IRQF_DISABLED,"key2",NULL); register_chrdev( major, "key", &key_ops); init_waitqueue_head(&wq); return 0; } static int hello_remove(struct platform_device *pdev) { free_irq(res1->start,NULL); free_irq(res2->start,NULL); unregister_chrdev( major, "key"); return 0; } static struct of_device_id key_id[]= { {.compatible = "fs4412,key" }, }; static struct platform_driver hello_driver= { .probe = hello_probe, .remove = hello_remove, .driver ={ .name = "bigbang", .of_match_table = key_id, }, }; static int hello_init(void) { printk("hello_init"); return platform_driver_register(&hello_driver); } static void hello_exit(void) { platform_driver_unregister(&hello_driver); printk("hello_exit \n"); return; } MODULE_LICENSE("GPL"); module_init(hello_init); module_exit(hello_exit);
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> main() { int fd,len; int key; fd = open("/dev/hello",O_RDWR); if(fd<0) { perror("open fail \n"); return ; } while(1) { read(fd,&key,4); printf("============key%d==================\n",key); } close(fd); }