一、中断基础概念
所谓中断,指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);
test.c
- #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);
- }