Linux中断-实操、概念

1、裸机中的中断处理方法:

(1)使能中断、初始化相应寄存器

(2)注册中断服务函数,向irqTable数组的指定标号处写入中断服务函数

(3)中断发生后进入IRQ中断服务函数,执行对应中断处理函数

2Linux中断API函数

1)中断号

每个中断都有一个中断号,通过中断号可区分不同中断。

2Linux中断申请

Linux内核中想使用一个中断是需要申请的,request_irq函数用于申请中断,request_irq可能会导致睡眠,不能在中断上下文中申请。

参数

  • irq:要申请中断的中断号
  • handler:中断处理函数
  • flags:中断标志位,定义在include/linux/interrupt.h
  • name:中断名字,可在/proc/interrupts中看到
  • dev:若将flags设置为IRQF_SHARED(该中断线被多个设备共享),dev用来区分不同中断,一般情况下,dev设置为设备结构体,dev会传递给handler第二个参数。

Request_irq会激活(使能)中断。

包括上升沿、下降沿等触发方式,可以用|来实现多种组合。

3)释放中断

free_irq函数。中断完成后要释放掉响应的中断

  • 第一个参数irq:要释放的中断
  • 第二个参数dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数时才被禁止。

4)中断处理函数

  • 第一个参数int:中断处理函数要处理的中断号
  • 第二个参数void*:通用型指针,用于区分共享中断的不同设备,与request_irq函数的dev参数保持一致。

5)中断使能和禁止函数

关闭指定中断:

  • irq:要禁止的中断号

关闭整个中断系统:

3、上半部与下半部

Linux中中断分为上半部和下半部

上半部:就是中断处理函数,执行速度快的操作放在上半部

下半部:耗时长的放在下半部,以免阻塞系统运行时间过长。

4、下半部的三种实现机制

1)软中断

(软中断和硬中断相比,就是触发方式不同,由硬件触发变为软件触发,同样会去打断当前进程的执行)

Linux内核中,软中断被设计为在所有硬件中断处理完后才执行。

Linux中硬中断上下文要关中断,无法被其他硬中断打断(禁止中断嵌套)。软中断不关中断,可以被打断。

  • Linux中使用softirq_action表示软中断

                

  • /kernel/softirq.c文件中一共定义了10软中断

                

                        Softirq_action结构体中的action成员变量就是软中断的服务函数

  • 注册软中断处理函数

                         使用软中断要先注册对应的软中断处理函数,open_softirq

                

                        nr:要开启的软终端

                        action:软中断对应的处理函数

  • 触发软中断

                        软中断注册好后要通过raise_softirq函数触发

                        

                        nr:要触发的软中断

软中断流程:

        a、定义软中断处理函数

        b、注册软中断处理函数

        c、触发软中断

2tasklet

tasklet是利用软中断实现的另一种下半部机制。

  • tasklet结构体:

                Linux 内核使用 tasklet_struct 结构体来表示 tasklet                        

                func是tasklet要执行的处理函数,用户定义的函数内容,相当于中断处理函数。使用tasklet前,必须先定义初始化一个tasklet

  • 初始化tasklet

                方式一:

        ​​​​​​​        

        ​​​​​​​        

                        t:要初始化的tasklet

                        func:tasklet的处理函数

                        data:要传递给func函数的参数

                方式二:

        ​​​​​​​        ​​​​​​​        

                        也可以使用宏 DECLARE_TASKLET 来一次性完成 tasklet 的定义和初始化,

                        name:定义的tasklet名字

                        func:tasklet处理函数

                        data:传递给func函数的参数

  • 调用tasklet

                在中断上半部中调用tasklet_schedule函数就能使得tasklet在合适的时间运行

        ​​​​​​​        

                        t:要调度的tasklet

tasklet流程

        a、定义tasklet结构体

        b、定义tasklet处理函数(一个普通的函数)

        c、初始化tasklet

        d、注册中断服务函数

        e、中断服务函数中调度tasklet

3)工作队列:

另一种下半部执行方式,在进程上下文执行,工作队列将要推后的工作交给一个内核线程执行,工作在进程上下文中

工作

        内核中用work_struct结构体表示一个工作

        

工作队列

        这些工作会组织成一个工作队列,使用workqueue_struct结构体表示

工作线程worker thread

        Linux内核中会使用工作者线程处理工作队列中的各个工作,用worker结构体表示工作者线程。

工作、工作队列、工作线程(工作者)关系

        每个worker都有一个工作队列,工作者线程处理自己工作队列中的所有工作(每个CPU都有个工作者线程,并行处理多个工作)。

        实际开发中,我们只需要定义工作(work_struct)即可,工作队列和工作线程基本不用管。

工作创建

        1)定义一个work_strcut结构体变量

        2)初始化工作

方式一:

#define INIT_WORK(_work, _func)

INIT_WORK宏初始化工作

  • _work:要初始化的工作
  • _func:工作对应的处理函数

方式二:

#define DECLARE_WORK(n, f)

DECLARE_WORK 宏一次性完成工作的创建和初始化

  • n:要定义的工作
  • f:工作对应的处理函数

3)调度工作

需要调用工作才能运行

bool schedule_work(struct work_struct *work)

  • work:要调度的工作
  • 返回值:0 成功,其他值,失败

同样要在中断的上半部,主动开启调度。

4)threaded irq:(现在的新技术)

单核的时候工作队列只有一个内核线程,还可以,但是如果用到多核,其他核心就浪费了,所以给每个线程都创建一个线程,有专门的线程中断函数,thread_fn中放入线程里要执行的函数。这样子线程平均的分配到多个核心中。

可以只提供 thread_fn,系统会为这个函数创建一个内核线程。发生中断时,内核线程就会执行这个函数。

5、设备树中断信息节点

中断控制器节点

GIC中断控制器节点

  • #interrupt-cells:该中断控制器下设备的cell大小。第一个cell:中断类型,0表示SPI中断,1表示PPI中断。第二个cell:中断号。SPI中断为0~987,PPI中断号为0~15。第三个cell:标志,bit[3:0]表示中断触发类型,为1时表示上升沿触发,为2时表示下降沿触发等。bit[15:8]为PPI中断的CPU掩码
  • interrupt-controller:为空,表示当前节点为中断控制器

GPIO节点中断控制器:gpio5

  • interrupts:描述中断源信息,gpio5一共两条中断源信息,类型都为SPI,触发电平都为IRQ_TYPE_LEVEL_HIGH,不同之处在中断源,一个是74、一个是75

查表可知GPIO5一共用了两个中断号,一个是74、一个是75,分别对应GPIO5_IO00~GPIO5_IO15和GPIO5_IO16~GPIO5_IO31

  • interrupt-controller:表明gpio5节点也是个中断控制器,用于控制所有gpio5所有IO的中断
  • #interrupt-cells:修改为2

Fxls8471是6ULL开发板上的磁力计芯片,有一个中断引脚连接到了SNVS_TAMPER0 引脚上,这个引脚可以复用为 GPIO5_IO00。

  • interrupt-parent 属性:设置中断控制器,使用gpio5作为中断控制器
  • interrupt:设置中断信息,0表示GPIO5_IO00,8表示低电平触发

#interrupt-cells,指定中断源信息的cell个数

interrupt-controller,当前节点为中断控制器

interrupt,指定中断号,触发方式

interrupt-parent,指定父中断,中断控制器

6、设备树里中断

imx6ull.dtsi(厂家提供的)、 100ask_imx6ull-14x14.dts

imx6ull.dtsi:中包含了厂家定义好的中断控制器

100ask_imx6ull-14x14.dts :只需要在外部设备中指定好中断

 

BSP:工程师定义的两个中断控制器(GIC、GPIO等)

(1)GIC中断控制器(厂家):

        1、compatibel:找到对应的驱动程序
        2、interrupt-controller:说明这是一个中断控制器

        3、#interrupt-controller:使用该中断控制器的cell(参数个数)数量

(2)GPIO等中断控制器(厂家):

        1、compatibel:找到对应的驱动程序
        2、interrupt-controller:说明这是一个中断控制器

        3、#interrupt-controller:使用该中断控制器的cell(参数个数)数量

        4、使用上一级哪个中断控制器的哪一个中断

                Interrupt_parent = <& >

                Interrupts = < >  < >  描述一个或多个中断(使用到多个中断时)

(3)指定使用哪一个中断控制器里的哪个中断(我们自己定义)

        1、指定使用哪一个中断控制器里的哪个中断

                Interrupt_parent = <& >

                Interrupts = < >  < >  描述一个或多个中断(使用到多个中断时)

 

GIC中通常Cell有三个属性(下一级中断cell的参数):

1、类(私有中断PPI、还是共享中断SPI)

2、哪个:哪个中断

3、电平触发类型:

要使用 gpio1 里的第 5 号中断, hwirq 就是 5。

7、获取中断号

中断驱动程序编写时要用到中断号,中断信息已写入到设备树中。有两种获取方式。可以用irq_of_parse_and_map函数从interupts属性中提取到对应的设备号

函数参数和返回值含义如下:

  • dev:设备节点
  • index:索引号,interrupt属性中,可能包含多条中断信息,通过index指定要获取的信息

返回值:中断号

若该引脚使用到了GPIO,可以用gpio_to_irq函数获取gpio对应的中断号

  • gpio:要获取的GPIO编号:要获取的GPIO编号

返回值:GPIO对应的中断号

8、按键中断程序过程

a、定义一个软件定时器,完成其初始化,定义其处理函数

b、初始化keyIo,gpio子系统获取设备树信息

c、定义中断服务函数、获取中断号,申请中断

9、运行

cat /proc/interrupts

中断已经成功注册

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT		1			/* 设备号个数 	*/
#define IMX6UIRQ_NAME		"imx6uirq"	/* 名字 		*/
#define KEY0VALUE			0X01		/* KEY0按键值 	*/
#define INVAKEY				0XFF		/* 无效的按键值 */
#define KEY_NUM				1			/* 按键数量 	*/

/* 中断IO描述结构体 */
struct irq_keydesc {
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	atomic_t keyvalue;		/* 有效的按键键值 */
	atomic_t releasekey;	/* 标记是否完成一次完成的按键,包括按下和释放 */
	struct timer_list timer;/* 定义一个定时器*/
	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键描述数组 */
	unsigned char curkeynum;				/* 当前的按键号 */
};

struct imx6uirq_dev imx6uirq;	/* irq设备 */

/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

	dev->curkeynum = 0;
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后
 *				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg	: 设备结构变量
 * @return 		: 无
 */
void timer_function(unsigned long arg)
{
	unsigned char value;
	unsigned char num;
	struct irq_keydesc *keydesc;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

	num = dev->curkeynum;
	keydesc = &dev->irqkeydesc[num];

	value = gpio_get_value(keydesc->gpio); 	/* 读取IO值 */
	if(value == 0){ 						/* 按下按键 */
		atomic_set(&dev->keyvalue, keydesc->value);
	}
	else{ 									/* 按键松开 */
		atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
		atomic_set(&dev->releasekey, 1);	/* 标记松开按键,即完成一次完整的按键过程 */			
	}	
}

/*
 * @description	: 按键IO初始化
 * @param 		: 无
 * @return 		: 无
 */
static int keyio_init(void)
{
	unsigned char i = 0;
	int ret = 0;
	
	imx6uirq.nd = of_find_node_by_path("/key");
	if (imx6uirq.nd== NULL){
		printk("key node not find!\r\n");
		return -EINVAL;
	} 

	/* 提取GPIO */
	for (i = 0; i < KEY_NUM; i++) {
		imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
		if (imx6uirq.irqkeydesc[i].gpio < 0) {
			printk("can't get key%d\r\n", i);
		}
	}
	
	/* 初始化key所使用的IO,并且设置成中断模式 */
	for (i = 0; i < KEY_NUM; i++) {
		memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name));	/* 缓冲区清零 */
		sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);		/* 组合名字 */
		gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
		gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);	
		imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
		imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
		printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio, 
                                         imx6uirq.irqkeydesc[i].irqnum);
	}
	/* 申请中断 */
	imx6uirq.irqkeydesc[0].handler = key0_handler;
	imx6uirq.irqkeydesc[0].value = KEY0VALUE;
	
	for (i = 0; i < KEY_NUM; i++) {
		ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, 
		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
		if(ret < 0){
			printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
			return -EFAULT;
		}
	}

	/* 创建定时器 */
	init_timer(&imx6uirq.timer);
	imx6uirq.timer.function = timer_function;
	return 0;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &imx6uirq;	/* 设置私有数据 */
	return 0;
}

 /*
  * @description     : 从设备读取数据 
  * @param - filp    : 要打开的设备文件(文件描述符)
  * @param - buf     : 返回给用户空间的数据缓冲区
  * @param - cnt     : 要读取的数据长度
  * @param - offt    : 相对于文件首地址的偏移
  * @return          : 读取的字节数,如果为负值,表示读取失败
  */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

	keyvalue = atomic_read(&dev->keyvalue);
	releasekey = atomic_read(&dev->releasekey);

	if (releasekey) { /* 有按键按下 */	
		if (keyvalue & 0x80) {
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
	} else {
		goto data_error;
	}
	return 0;
	
data_error:
	return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
	.owner = THIS_MODULE,
	.open = imx6uirq_open,
	.read = imx6uirq_read,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init imx6uirq_init(void)
{
	/* 1、构建设备号 */
	if (imx6uirq.major) {
		imx6uirq.devid = MKDEV(imx6uirq.major, 0);
		register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
	} else {
		alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
		imx6uirq.major = MAJOR(imx6uirq.devid);
		imx6uirq.minor = MINOR(imx6uirq.devid);
	}

	/* 2、注册字符设备 */
	cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
	cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

	/* 3、创建类 */
	imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
	if (IS_ERR(imx6uirq.class)) {
		return PTR_ERR(imx6uirq.class);
	}

	/* 4、创建设备 */
	imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
	if (IS_ERR(imx6uirq.device)) {
		return PTR_ERR(imx6uirq.device);
	}
	
	/* 5、初始化按键 */
	atomic_set(&imx6uirq.keyvalue, INVAKEY);
	atomic_set(&imx6uirq.releasekey, 0);
	keyio_init();
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit imx6uirq_exit(void)
{
	unsigned int i = 0;
	/* 删除定时器 */
	del_timer_sync(&imx6uirq.timer);	/* 删除定时器 */
		
	/* 释放中断 */
	for (i = 0; i < KEY_NUM; i++) {
		free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
		gpio_free(imx6uirq.irqkeydesc[i].gpio);
	}
	cdev_del(&imx6uirq.cdev);
	unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
	device_destroy(imx6uirq.class, imx6uirq.devid);
	class_destroy(imx6uirq.class);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值