Exynos4412 中断驱动开发(一)—— 中断基础及中断的注册过程

一、中断基础概念

        所谓中断,指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);
}




你可能感兴趣的:(linux,注册过程,中断驱动)