01-驱动中的中断处理

本系列文章主要讲Linux中的中断和时间管理,文章机构如下:
01 - 驱动中的中断处理
02 - 中断下半部 tasklet
03 - 中断的下半部 workqueue
04 - Linux中的延时操作函数
05 - Linux硬件定时 jiffies
06 - Linux 低分辨率定时器
07 - Linux高分辨率定时器

1. 驱动中定义的中断处理相关的API

1.1 注册中断函数

 驱动中注册中断处理函数的原型如下:

原    型: static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
功    能: 向内核中注册中断处理函数
@param1: 设备上所用中断号,这个中断号不是硬件手册上查到的号,而是内核中的中断号,这个号将会用于决定构造的 struct irqaction 对象被插入到哪个链表,并用于初始化 struct irqaction 对象中的irq成员
@param2: 指向中断处理函数的指针
@param3: 与中断相关的标志,用于初始化 struct irqaction 对象中的flags成员,这些中断标志可以用位或的方式来设置多个,常用的标志如下:
					#define IRQF_TRIGGER_RISING		0x00000001		// 上升沿触发		 在 include\linux\interrupt.h 中定义
					#define IRQF_TRIGGER_FALLING	0x00000002		// 下降沿触发
					#define IRQF_TRIGGER_HIGH		0x00000004		// 高电平触发
					#define IRQF_TRIGGER_LOW		0x00000008		// 低电平触发
					#define IRQF_SHARED				0x00000080		// 共享中断标志,多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq 函数的 dev 参数就是唯一区分他们的标志。
					#define IRQF_TIMER				(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)	// 定时器专用中断标志
					#define __IRQF_TIMER			0x00000200
					#define IRQF_NO_SUSPEND			0x00004000
					#define IRQF_NO_THREAD			0x00010000
@param4: 该中断在 /proc 中的名字,用于初始化 struct irqaction 对象中的name成员
@param5: 区别不同设备所对应的 struct irqaction 对象,在 struct irqaction 对象从链表中移除时需要,dev用于初始化 struct irqaction 对象中的dev_id成员。共享中断必须传递一个非NULL实参,非共享中断可以传NULL。中断发生后调用中断处理函数时也会把该参数传递给中断处理函数。
@retval: 成功返回0,失败返回负的错误码。

 需要注意的是:request_irq 函数根据传入的参数构造好一个 struct irqaction 对象并加入到对应的链表后,还将对应的中断使能了,因此我们不需要再使能中断。

 其中 struct irqaction 结构体的定义如下:

struct irqaction {
	irq_handler_t		handler;			// 中断处理函数的指针
	void				*dev_id;			// 区别共享中断中不同设备的ID
	void __percpu		*percpu_dev_id;
	struct irqaction	*next;				// 将共享同一IRQ号的 struct irqaction 对象链接在一起的指针
	irq_handler_t		thread_fn;
	struct task_struct	*thread;
	struct irqaction	*secondary;
	unsigned int		irq;				// IRQ号
	unsigned int		flags;
	unsigned long		thread_flags;		// 以IRQF_为开头的一组标志
	unsigned long		thread_mask;
	const char		*name;
	struct proc_dir_entry	*dir;
} ____cacheline_internodealigned_in_smp;

1.2 中断处理函数

 中断处理函数原型如下:

原    型: irqreturn_t (*irq_handler_t)(int, void *); include\linux\irqreturn.h
功    能: 中断处理函数,中断发生后中断处理函数会自动调用
@param1: IRQ号
@param2: 对应的设备ID,也是 struct irqaction 对象中的dev_id成员
@retval: 返回值是一个枚举类型,在 include\linux\irqreturn.h 中定义,详细信息如下			
			enum irqreturn {
				IRQ_NONE		= (0 << 0),		// 不是驱动所管理的设备产生的中断,用于共享中断
				IRQ_HANDLED		= (1 << 0),		// interrupt was handled by this device
				IRQ_WAKE_THREAD	= (1 << 1),		// handler requests to wake the handler thread
			};

 一般中断处理函数返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED)

 IRQ_RETVAL宏的定义如下:

#define IRQ_RETVAL(x)	((x) ? IRQ_HANDLED : IRQ_NONE)

1.3 注销中断的函数

 注销中断处理函数的原型如下:

原    型: void free_irq(unsigned int, void *) 	
功    能: 注销一个中断处理函数
@param1: IRQ号
@param2: 对应的设备ID,也是 struct irqaction 对象中的dev_id成员,共享中断必须要传递一个非NULL的实参,和request_irq中的dev_id保持一致
@retval: 无返回值

1.4 使能及禁止使能相关函数

 除此之外,还有一些中断常用的使能和禁止的函数如下:

void enable_irq(unsigned int irq)		// 使能指定的中断
void disable_irq(unsigned int irq)		// 禁止指定的中断

 disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:

void disable_irq_nosync(unsigned int irq)

 disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

 上面三个函数都是使能或者禁止某一个中断,有时候我们需要关闭当前处理器的整个中断系统,这个时候可以使用如下两个函数:

local_irq_enable()		// 使能当前处理器中断系统
local_irq_disable()		// 禁止当前处理器中断系统
// 函数原型如下
#define local_irq_enable() do { } while (0)
#define local_irq_disable() do { } while (0)

local_irq_save(flags) 		// 用于禁止中断,并且将中断状态保存在 flags 中
local_irq_restore(flags)	// 用于恢复中断,将中断恢复到 flags 状态

 中断处理函数应该快速完成,不能消耗太长时间。因为处理进入中断后相应的中断被屏蔽,在之后的代码中也没有重新开启,因此整个中断处理过程中中断是禁止的,如果中断处理函数执行的过长,其他的中断将会被挂起,从而对其他中断的相应造成严重的影响。
 必须记住的一条准则是:在中断处理函数中一定不能调用可能引起进程切换的函数,因为一旦中断处理程序被切换将不能再次被调度,这是内核对终端处理的一个严格限制。学过的函数有 copy_from_user 、copy_to_user

2. 示例代码

2.1 demo.c

 在代码的112行添加了注册中断的函数,中断处理函数是my_irq_handler,因为定义了共享中断所以必须传递dev_id。产生中断事件之后会自动的调用50行的中断处理函数,在里面可以做中断相关的内容,用宏 IRQ_RETVAL(IRQ_HANDLED) 作为中断处理函数的返回值。
 本实例中只是搭建了中断处理的框架,并没有进行实际的操作,可以利用按键来模拟外部中断。

#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/atomic.h>
#include <linux/slab.h>				// kzalloc和kfree的头文件
#include <linux/interrupt.h>		// 中断相关的头文件

typedef struct 
{
	dev_t dev_no;
	char devname[20];
	char classname[20];
	struct cdev demo_cdev;
	struct class *cls;
	struct device *dev;
	struct mutex my_mutex;			// 定义一个互斥体,在init函数中进行初始化
}demo_struct;
demo_struct *my_demo_dev = NULL;	// 定义一个设备结构体指针,指向NULL

static int demo_open(struct inode *inode, struct file *filp)
{
	/* 首先判断设备是否可用 */
	if( mutex_lock_interruptible(&my_demo_dev->my_mutex) )		// 访问共享资源之前获取互斥体,成功获取返回0
	{
		return -ERESTARTSYS;		// 不能获取返回错误码
	}
	
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	return 0;

}

static int demo_release(struct inode *inode, struct file *filp)
{
	/* 释放互斥量 */
	mutex_unlock(&my_demo_dev->my_mutex);

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	return 0;
}

struct file_operations demo_ops = {
	.open = demo_open,
	.release=  demo_release,
};

irqreturn_t my_irq_handler(int irq, void *dev_id)
{
	/*
		中断处理函数执行内容
	*/
	
	return IRQ_RETVAL(IRQ_HANDLED);		// 中断处理函数常用的返回值
}


static int __init demo_init(void)
{
	int ret;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	/* 开辟空间 */
	my_demo_dev =  kzalloc(sizeof(demo_struct), GFP_KERNEL);
	if ( IS_ERR(my_demo_dev) )
	{
		printk("kzalloc failed.\n");
		ret = PTR_ERR(my_demo_dev);
		goto kzalloc_err;
	}
	strcpy(my_demo_dev->devname, "demo_chrdev");	// 给设备名字赋值
	strcpy(my_demo_dev->classname, "demo_class");	// 给设备类的名字赋值

	mutex_init(&my_demo_dev->my_mutex);				// 初始化互斥体
	
	ret = alloc_chrdev_region(&my_demo_dev->dev_no, 0, 0, my_demo_dev->devname);
	if (ret)
	{
		printk("alloc_chrdev_region failed.\n");
		goto region_err;
	}

	cdev_init(&my_demo_dev->demo_cdev, &demo_ops);

	ret = cdev_add(&my_demo_dev->demo_cdev, my_demo_dev->dev_no, 1);
	if (ret < 0)
	{
		printk("cdev_add failed.\n");
		goto add_err;
	}

	my_demo_dev->cls = class_create(THIS_MODULE, my_demo_dev->classname);		/* 在目录/sys/class/.. */
	if ( IS_ERR(my_demo_dev->cls) )
	{
		ret = PTR_ERR(my_demo_dev->cls);
		printk("class_create failed.\n");
		goto cls_err;
	}

	my_demo_dev->dev = device_create(my_demo_dev->cls, NULL, my_demo_dev->dev_no, NULL, "chrdev%d", 0);	/* 在目录/dev/.. */
	if ( IS_ERR(my_demo_dev->dev) )
	{
		ret = PTR_ERR(my_demo_dev->dev);
		printk("device_create failed.\n");
		goto dev_err;
	}

	// 注册中断,因为是共享中断所以必须传递dev_id
	ret = request_irq(123, my_irq_handler, IRQF_TRIGGER_RISING | IRQF_SHARED, "irq_test", my_demo_dev);	
	if ( ret )	// 成功返回0,失败返回错误码
	{	
		printk("request_irq failed.\n");
		goto irq_err;
	}
	return 0;

irq_err:
	device_destroy(my_demo_dev->cls, my_demo_dev->dev_no);
dev_err:
	class_destroy(my_demo_dev->cls);	
cls_err:
	cdev_del(&my_demo_dev->demo_cdev);
add_err:
	unregister_chrdev_region(my_demo_dev->dev_no, 1);
region_err:
	kfree(my_demo_dev);		// 释放空间,避免内存泄漏
kzalloc_err:
	return ret;
}


static void __exit demo_exit(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	free_irq(123, my_demo_dev);		// 注销中断
	device_destroy(my_demo_dev->cls, my_demo_dev->dev_no);
	class_destroy(my_demo_dev->cls);
	cdev_del(&my_demo_dev->demo_cdev);
	unregister_chrdev_region(my_demo_dev->dev_no, 1);
	kfree(my_demo_dev);				// 释放空间,避免内存泄漏
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

2.2 test.c

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd;
	int i, count = 1;

	fd = open("/dev/chrdev0", O_RDWR, 0666);
	if (fd < 0)
	{
		perror("open");
		return -1;
	}

	while(1);

	close(fd);

	return 0;
}

2.3 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	arm-linux-gnueabihf-gcc test.c -o app
install:
	sudo cp *.ko  app /tftpboot
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值