Linux驱动——中断

目录

一、Linux中断处理函数

1.中断号

2.request_irq函数

3.free_irq函数

4.中断处理函数

5.中断使能与禁止函数

6.中断下半部分

1.软中断

2.tasklet

3.工作队列

二、中断驱动编写

1.添加设备树信息

2.获取到中断号

3.中断驱动框架


一、Linux中断处理函数

1.中断号

每个中断都有一个中断号,通过中断号即可区分不同的中断,有的资料也把中断号叫做中
断线。

获取中断号:

(1)根据路径获取设备树中的节点

struct device_node *of_find_node_by_path(const char *path);

const char *path:设备树中节点路径

返回值:获取到的节点

(2) 根据节点获取到节点的中断号

unsigned int irq_of_parse_and_map(struct device_node *dev,int index)

struct device_node *dev:设备节点

int index:获取节点中第几个中断号

返回值:获取到的中断号

2.request_irq函数

        在 Linux 内核中要想使用某个中断是需要申请的,request_irq 函数用于申请中断request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断。

函数原型:
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev)

unsigned int irq:设备对应的中断号

irq_handler_t handler:中断的处理函数
typedef irqreturn_t (*irq_handler_t)(int, void *);

unsigned long flags:中断的触发方式
#define IRQF_TRIGGER_NONE 0x00000000    //无触发
#define IRQF_TRIGGER_RISING 0x00000001  //上升沿触发
#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿触发
#define IRQF_TRIGGER_HIGH 0x00000004    //高电平触发
#define IRQF_TRIGGER_LOW 0x00000008     //低电平触发

const char *name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。

void *dev:传递给中断处理函数 irq_handler_t 的第二个参数。

返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经
被申请了。

3.free_irq函数

        使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放
掉相应的中断。

函数原型:

void free_irq(unsigned int irq,void *dev)

irq :要释放的中断。

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

返回值:无。

4.中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:

irqreturn_t (*irq_handler_t) (int, void *)

int:要中断处理函数要相应的中断号。

void *:一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构。

中断处理函数的返回值为 irqreturn_t 类型,irqreturn_t 类型定义如下所示:

 enum irqreturn {
     IRQ_NONE = (0 << 0),
     IRQ_HANDLED = (1 << 0),
     IRQ_WAKE_THREAD = (1 << 1),
 };

 typedef enum irqreturn irqreturn_t;

         可以看出 irqreturn_t 是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式:
        return IRQ_RETVAL(IRQ_HANDLED)

5.中断使能与禁止函数

中断使能:void enable_irq(unsigned int irq)
中断禁止:void disable_irq(unsigned int irq)

irq 就是要禁止的中断号

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

void disable_irq_nosync(unsigned int irq),函数调用以后立即返回

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

local_irq_enable()
local_irq_disable()

6.中断下半部分

上半部:

        上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。


下半部:

        如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。

        上半部处理很简单,直接编写中断处理函数就行了,关键是下半部该怎么做呢?Linux 内
核提供了多种下半部机制,分别为软中断、tasklet、工作队列。

1.软中断

        处理速度比较块,但是是内核级别的机制,要修改内核源码,不推荐也不常用

2.tasklet

        tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使用 tasklet。Linux 内核使用 tasklet_struct 结构体来表示 tasklet:

1. 初始化,设置tasklet任务结构体
 struct tasklet_struct
 {
     struct tasklet_struct *next; /* 下一个 tasklet */
     unsigned long state;         /* tasklet 状态 */
     atomic_t count;              /* 计数器,记录对 tasklet 的引用数 */
     void (*func)(unsigned long); /* tasklet 执行的函数 */
     unsigned long data;          /* 函数 func 的参数 */
 };

        如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet,taskled_init 函数原型如下:

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);

        tasklet_init(&任务结构体的地址,中断下半部分执行函数,函数参数);

2. 启动中断下半部分----把任务放入到内核线程中
tasklet_schedule(&tasklet);

 示例代码:

/* 定义 taselet */
struct tasklet_struct testtasklet;

/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
    /* tasklet 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
    ......
    /* 调度 tasklet */
    tasklet_schedule(&testtasklet);
    ......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
    ......
    /* 初始化 tasklet */
    tasklet_init(&testtasklet, testtasklet_func, data);
    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
    ......
}

3.工作队列

        工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的
工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重
新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软
中断或 tasklet。

1. 初始化,设置workqueue任务结构体
struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;  /* 工作队列处理函数 */
};
INIT_WORK(&任务结构体的地址,中断下半部分执行函数);

         和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原
型如下所示:

bool schedule_work(struct work_struct *work)

2. 启动中断下半部分----把任务放入到内核线程中
//中断下半部分启动--放入内核线程
schedule_work(&workqueue);

 示例代码 :

/* 定义工作(work) */
struct work_struct testwork;

/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
    /* work 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
    ......
    /* 调度 work */
    schedule_work(&testwork);
    ......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
    ......
    /* 初始化 work */
    INIT_WORK(&testwork, testwork_func_t);
    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
    ......
}

二、中断驱动编写

接下来我们以exynos4412-fs4412开发板的k3控制蜂鸣器为例。

硬件连接图: key3——GPX1_2——XEINT10

蜂鸣器硬件连接图:

 1.添加设备树信息

(1)修改要下载到硬件设备的设备树,做设备信息的添加

设备树位置:vi arch/arm/boot/dts/exynos4412-fs4412.dts
在编程过程中,需要添加中断设备节点信息--描述当前设备的中断号

key_int_node {
    compatible = "key3";
    interrupt-parent = <&gpx1>;
    interrupts = <2 4>;
};

(2)重新编译修改后的设备树,并把新编译的设备树拷贝到 "tftpboot" 目录下

2.获取到中断号

在驱动中获取到中断号,并申请中断处理

(1)获取到中断号

//根据路径获取设备树中的节点
struct device_node *of_find_node_by_path(const char *path);

(2)申请中断处理(内核检查到对应的中断,驱动申请进行处理) 

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)

3.中断驱动框架

//外设信息对象
struct xxx_node
{
	unsigned int major;
	struct class * cls;
	struct device * dev;
	unsigned int irqno;
};
struct xxx_node xxx;

//中断处理函数
irqreturn_t xxx_handler(int irqno, void * dev) 
{
	.........
	return IRQ_HANDLED;
}

//文件IO接口实现
const struct file_operations fops=
{
    .......
};

//驱动入口函数
static int __init xxx_init(void)
{

	//申请设备号
	xxx.major = register_chrdev(0,"xxx",&fops);
	if(xxx.major < 0)
	{
		printk("register_chrdev error\n");
		ret = -1;
		goto err_0;
	}

	//创建设备节点
	xxx.cls = class_create(THIS_MODULE,"xxx cls");
	if(IS_ERR(xxx.cls))
	{
		printk("class_create error\n");
		ret = -2;
		goto err_1;
	}

	xxx.dev = device_create(xxx.cls,NULL,MKDEV(xxx.major,0),NULL,"xxx");
	if(IS_ERR(xxx.dev))
	{
		printk("device_create error\n");
		ret = -3;
		goto err_2;
	}

	//地址映射、硬件设备初始化
    ioremap(物理地址,映射大小);
	...............

	//中断初始化,获取中断号
	struct device_node * np = of_find_node_by_path("/xxx_int_node");
	if(np == NULL)
	{
		printk("of_find_node_by_path error\n");
		ret = -4;
		goto err_3;
	}
	key.irqno = irq_of_parse_and_map(np,0);

	//申请中断
	if( request_irq(xxx.irqno,xxx_handler,IRQF_TRIGGER_FALLING,"this is xxx","hello world") != 0 )
	{
		printk("request_irq error\n");
		ret = -5;
		goto err_3;
	}

	return 0;



//驱动卸载入口
static void __exit xxx_exit(void)
{
    //释放中断
	free_irq(xxx.irqno,"hello world");
	
    //销毁设备节点
	device_destroy(xxx.cls,MKDEV(key.major,0));
	class_destroy(xxx.cls);
	
    //释放设备号
	unregister_chrdev(xxx.major,"xxx");

    //解除地址映射
		iounmap();
        return 0;

//出错处理
err_3:
	device_destroy(xxx.cls,MKDEV(xxx.major,0));
	
err_2:
	class_destroy(xxx.cls);

err_1:
	unregister_chrdev(xxx.major,"xxx");

err_0:
	return ret;

}


module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

按键控制蜂鸣器实现代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#define GPD0CON 0x114000A0
#define PWM 0x139D0000
#define PWMTCFG0   0x139D0000
#define PWMTCFG1   0x139D0004
#define PWMTCON    0x139D0008
#define PWMTCNTB0  0x139D000C
#define PWMTCMPB0  0x139D0010

//蜂鸣器信息结构体
struct BEEP
{
	unsigned int major;
	struct class * cls;
	struct device * dev;

	unsigned int * pwmtcfg0;
	unsigned int * pwmtcfg1;
	unsigned int * pwmtcon;
	unsigned int * pwmtcntb0;
	unsigned int * pwmtcmpb0;
	unsigned int * gpd0con;

};
struct BEEP beep;

//按键信息对象
struct Key_node
{
	unsigned int major;
	struct class * cls;
	struct device * dev;
	unsigned int irqno;
};
struct Key_node key;

//中断处理函数
irqreturn_t key_handler(int irqno, void * dev) 
{
	printk("%s\n",(char *)dev);
	static int i=1;i++;
	if(i%2==1)
		{
	*(beep.pwmtcon) |= 1;
		}
	if(i%2==0)
		{
	*(beep.pwmtcon) &= ~1;
		}

	return IRQ_HANDLED;
}

//文件IO处理函数
const struct file_operations fops;

//驱动入口函数
static int __init key_init(void)
{
	int ret;
	//申请设备号
	key.major = register_chrdev(0,"key",&fops);
	if(key.major < 0)
	{
		printk("register_chrdev error\n");
		ret = -1;
		goto err_0;
	}

	//创建设备节点

	key.cls = class_create(THIS_MODULE,"key cls");
	if(IS_ERR(key.cls))
	{
		printk("class_create error\n");
		ret = -2;
		goto err_1;
	}

	key.dev = device_create(key.cls,NULL,MKDEV(key.major,0),NULL,"key3");
	if(IS_ERR(key.dev))
	{
		printk("device_create error\n");
		ret = -3;
		goto err_2;
	}

	//硬件设备初始化
	beep.gpd0con = ioremap(GPD0CON,4);
	beep.pwmtcfg0 = ioremap(PWMTCFG0,4);
	beep.pwmtcfg1 = ioremap(PWMTCFG1,4);
	beep.pwmtcon = ioremap(PWMTCON,4);
	beep.pwmtcntb0 = ioremap(PWMTCNTB0,4);
	beep.pwmtcmpb0 = ioremap(PWMTCMPB0,4);

	*(beep.gpd0con) = *(beep.gpd0con) & ~(0XF<<0) | (0X2<<0);
	*(beep.pwmtcntb0) = 200000;
	*(beep.pwmtcmpb0) = 100000;
	*(beep.pwmtcfg0) = *(beep.pwmtcfg0) & ~0xff;
	*(beep.pwmtcfg1) &= ~0XF;
	*(beep.pwmtcon) &= ~(1<<4);
	*(beep.pwmtcon) |= 1<<3;
	*(beep.pwmtcon) |= 1<<1;
	*(beep.pwmtcon) &= ~(1<<1);
	*(beep.pwmtcon) |= 1<<2;

	//中断初始化,获取中断号
	struct device_node * np = of_find_node_by_path("/key_int_node");
	if(np == NULL)
	{
		printk("of_find_node_by_path error\n");
		ret = -4;
		goto err_3;
	}

	key.irqno = irq_of_parse_and_map(np,0);

	//申请中断
	if( request_irq(key.irqno,key_handler,IRQF_TRIGGER_FALLING,"this is key","hello world") != 0 )
	{
		printk("request_irq error\n");
		ret = -5;
		goto err_3;
	}

	return 0;

//出错处理	
err_3:
	device_destroy(key.cls,MKDEV(key.major,0));
	
err_2:
	class_destroy(key.cls);

err_1:
	unregister_chrdev(key.major,"key");

err_0:
	return ret;
}

//驱动卸载入口
static void __exit key_exit(void)
{
	free_irq(key.irqno,"hello world");
	
	device_destroy(key.cls,MKDEV(key.major,0));
		
	class_destroy(key.cls);
	
	unregister_chrdev(key.major,"key");
		iounmap(beep.pwmtcmpb0);
		iounmap(beep.pwmtcntb0);
		iounmap(beep.pwmtcon);
		iounmap(beep.pwmtcfg1);
		iounmap(beep.pwmtcfg0);
		iounmap(beep.gpd0con);

}

module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");



实验结果:按一下蜂鸣器响,再按下蜂鸣器停止。 

  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春风从不入睡、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值