linux驱动学习(九)之中断

一、中断的实现

对于中断的请求,在linux内核中,有一套标准的接口函数,可以实现中断的响应和处理。

#include <linux/interrupt.h>

//1 申请中断
static inline int __must_check request_irq(unsigned int irq, 
										irq_handler_t handler, 
										unsigned long flags, 
										const char *name, 
										void *dev)

1)unsigned int irq  ---->中断号,每个中断源有一个唯一中断号
2)irq_handler_t handler  --->中断请求发生后,被调用的中断服务函数 typedef irqreturn_t (*irq_handler_t)(int, void *); 

/**
 * enum irqreturn
 * @IRQ_NONE		interrupt was not from this device  中断发生异常时
 * @IRQ_HANDLED		interrupt was handled by this device 正常返回
 * @IRQ_WAKE_THREAD	handler requests to wake the handler thread
 */
enum irqreturn {
	IRQ_NONE		= (0 << 0),
	IRQ_HANDLED		= (1 << 0),
	IRQ_WAKE_THREAD		= (1 << 1),
};

typedef enum irqreturn irqreturn_t;

3)unsigned long flags ---->中断的标志----->中断触发方式
4) const char *name --->中断的名称,是由用户自定义
5)void *dev ---->向中断服务程序传递的参数 

返回值:
   成功:0
   失败:返回负数的错误码

查看开发板linux系统中的中断:

[root@GEC6818 /proc]#cat interrupts

	   CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7       
 33:          0          0          0          0          0          0          0          0       GIC  pl08xdmac
 34:          0          0          0          0          0          0          0          0       GIC  pl08xdmac
 37:          0          0          0          0          0          0          0          0       GIC  rtc 1hz
 39:         18         14         13         10         22         14         14         29       GIC  nxp-uart
 48:          0          0          0          1          0          0          0          0       GIC  s3c2440-i2c.1
 49:       3197        153       3935       3803       3735       3151       3387       3896       GIC  s3c2440-i2c.2
 56:      22946      15098      13068      14422      15060      15512      15454      18817       GIC  Event Timer IRQ
 63:          0          0          0          0          0          0          0          0       GIC  nxp-wdt
 65:       1402        876        712        851        786        901        919        982       GIC  nxp-disp
 66:          0          0          0          0          0          0          0          0       GIC  nxp-disp
 68:          0          0          1          0          0          0          0          0       GIC  hdmi-int
 中断号       中断被处理的次数                                                                    中断控制器   中断名称

在linux内核中,如果中断号被使用或者被其他申请了,再申请就会出错:

[root@GEC6818 /6818_driver]#insmod led_drv.ko 
[ 1022.779000] gec6818_interrupt_init
[ 1022.779000] ------------[ cut here ]------------
[ 1022.782000] WARNING: at kernel/irq/manage.c:1370 request_threaded_irq+0x114/0x13c()
[ 1022.789000] Modules linked in: led_drv(O+)
[ 1022.793000] [<c001517c>] (unwind_backtrace+0x0/0x134) from [<c0044880>] (warn_slowpath_common+0x54/0x64)
[ 1022.803000] [<c0044880>] (warn_slowpath_common+0x54/0x64) from [<c004492c>] (warn_slowpath_null+0x1c/0x24)
[ 1022.813000] [<c004492c>] (warn_slowpath_null+0x1c/0x24) from [<c00b8fd8>] (request_threaded_irq+0x114/0x13c)
[ 1022.822000] [<c00b8fd8>] (request_threaded_irq+0x114/0x13c) from [<bf00203c>] (gec6818_interrupt_init+0x3c/0x50 [led_drv])
[ 1022.833000] [<bf00203c>] (gec6818_interrupt_init+0x3c/0x50 [led_drv]) from [<c0008710>] (do_one_initcall+0x11c/0x174)
[ 1022.844000] [<c0008710>] (do_one_initcall+0x11c/0x174) from [<c0097dc0>] (sys_init_module+0xaa4/0x1e14)
[ 1022.853000] [<c0097dc0>] (sys_init_module+0xaa4/0x1e14) from [<c000ec40>] (ret_fast_syscall+0x0/0x30)
[ 1022.863000] ---[ end trace d4f7d5ddfdae4527 ]---
insmod: can't insert 'led_drv.ko': invalid parameter  ---->不合法参数,request_irq的参数不对

成功输出:

[root@GEC6818 /6818_driver]#insmod led_drv.ko 
[ 2711.551000] gec6818_interrupt_init
[root@GEC6818 /6818_driver]#lsmod
led_drv 977 0 - Live 0xbf008000 (O)
[root@GEC6818 /6818_driver]#cat /proc/interrupts 
134:          0          0          0          0          0          0          0          0      GPIO  my_interrupt
134 --->中断号 IRQ_GPIO_A_START + 28 --->106 + 28 --->134

//2 释放中断
void free_irq(unsigned int, void *)
参数说明:
	unsigned int ---->要释放的中断的中断号
	void * ----> 中断服务程序的参数

二、中断号

unsigned int irq

中断号是跟中断源有关,每一个中断源有一个唯一中断号,中断源由外部与内部,中断源是跟硬件有关,每一个处理器平台,中断源不一样的。

1、硬件平台的中断源的定义

kernel\arch\arm\mach-s5p6818\include\mach\s5p6818_irq.h
/*
* Physical Interrupt Number 64 (0~63)
*/
#define IRQ_PHY_UART1					(6	+ 32) // pl01115_Uart_modem
#define IRQ_PHY_UART0					(7	+ 32) // UART0_MODULE
#define IRQ_PHY_UART2					(8	+ 32) // UART1_MODULE
#define IRQ_PHY_UART3					(9	+ 32) // pl01115_Uart_nodma0
#define IRQ_PHY_UART4					(10 + 32)	// pl01115_Uart_nodma1
#define IRQ_PHY_UART5					(11 + 32)	// pl01115_Uart_nodma2

/*
 * GPIO Interrupt Number 160 (106~265)
 */
#define IRQ_GPIO_START			IRQ_PHY_MAX_COUNT
#define IRQ_GPIO_END			(IRQ_GPIO_START + 32 * 5)	// Group: A,B,C,D,E

#define IRQ_GPIO_A_START		(IRQ_GPIO_START + PAD_GPIO_A)
#define IRQ_GPIO_B_START		(IRQ_GPIO_START + PAD_GPIO_B)
#define IRQ_GPIO_C_START		(IRQ_GPIO_START + PAD_GPIO_C)
#define IRQ_GPIO_D_START		(IRQ_GPIO_START + PAD_GPIO_D)
#define IRQ_GPIO_E_START		(IRQ_GPIO_START + PAD_GPIO_E)	

关于中断号与GPIO:   KEY2->GPIOA28 
 按键       中断号                     GPIO口号        GPIO口
KEY2    IRQ_GPIO_A_START+28         PAD_GPIO_A+28   GPIOA28

头文件:#include <cfg_type.h>

三、中断标志

unsigned long flags

注意:中断一旦安装成功(request_irq),默认是打开的

中断标志分为:外部中断和内部中断

1、外部中断标志

#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

双边沿触发:IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING    

2、内核内部中断

#define IRQF_DISABLED	0x00000020   //当响应当前中断的时候,关闭其他的中断
#define IRQF_SHARED		0x00000080  	 //当一个中断源,注册多次时,可以对应多个中断服务程序
#define IRQF_TIMER		(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)  //当注册定时器中断时,使用该标志

四、中断服务程序

irq_handler_t handler

typedef irqreturn_t (*irq_handler_t)(int, void *);

通常情况下,一个中断源对应一个中断服务程序(中断处理函数),也可以多个中断源对应一个中断服务程序。

irqreturn_t key_irq_handler(int irq,void* dev)
{
  
}

1 根据中断号来区分中断处理
2 通过中断请求传递的参数来区分中断处理
头文件:#include <linux/interrupt.h>

注意:对于嵌入式系统中,中断服务程序是一个原子操作,所谓的原子操作就是该过程被执行时,不能被打断。 在中断服务服务程序中,不能有阻塞的操作,如睡眠,copy_to_user,copy_from_user,锁等。

在linux内核中,使用睡眠函数要包含头文件:

#include <linux/delay.h>

static inline void ssleep(unsigned int seconds) ---->秒级睡眠

void msleep(unsigned int msecs); ----->毫秒

五、中断与等待队列(wait_queue)

1、等待队列

设置一个等待条件,如果条件满足,则进程继续往下执行;如果条件不满足,则进程就进入等待队列中。当条件满足时,中断会唤醒等待队列中的进程,进程再继续往下执行。等待队列也是一种同步方法。

2、等待队列的相关API接口函数

#include <linux/wait.h>
#include <linux/sched.h>

//等待队列结构体:
typedef struct __wait_queue wait_queue_t;
struct __wait_queue 
{
	unsigned int flags;  //条件变量的标志
	#define WQ_FLAG_EXCLUSIVE	0x01
	void *private;
	wait_queue_func_t func;
	struct list_head task_list;
};

1)定义一个等待队列 和等待的条件
static wait_queue_t gec6818_wait;
static in key_press_flag = 0;

2)初始化等待队列
#define init_wait(wait)							\
	do {								\
	(wait)->private = current;				\
	(wait)->func = autoremove_wake_function;		\
	INIT_LIST_HEAD(&(wait)->task_list);			\
	(wait)->flags = 0;					\
} while (0)
static inline void INIT_LIST_HEAD(struct list_head *list)

由此可得:
void init_wait(wait_queue_t* wait);
如 init_wait(&gec6818_wait);

#define init_waitqueue_head(q)				\
	do {						\
	static struct lock_class_key __key;	\
	\
	__init_waitqueue_head((q), #q, &__key);	\
} while (0)

得到原型:void init_waitqueue_head(wait_queue_head_t*);

struct __wait_queue_head 
{
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
比如:wait_queue_head_t gec6818_wq_head;

3)进入等待队列
#define wait_event_interruptible(wq, condition)				\
	({									\
	int __ret = 0;							\
	if (!(condition))						\
	__wait_event_interruptible(wq, condition, __ret);	\
	__ret;								\
})
struct __wait_queue_head {
	spinlock_t lock;
	struct list_head task_list;
};

typedef struct __wait_queue_head wait_queue_head_t;
void wait_event_interruptible(wait_queue_head_t wait,int condition)

4)唤醒队列
#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)

void wake_up(wait_queue_head_t* head)

觉得有帮助的话,打赏一下呗。。

相关推荐
菜鸡00013 天前
Ubuntu 20 掉显卡驱动的解决办法
linux·经验分享·笔记·ubuntu·驱动
极客代码1 个月前
【Linux】设备驱动中的ioctl详解
linux·内核·驱动·设备驱动·iocto
不知火猪2 个月前
最新雷蛇鼠标键盘驱动Razer Synapse 4(雷云) 下载与安装
计算机外设·驱动·雷云·雷蛇驱动
楼兰公子2 个月前
相机主要调试参数
arm开发·驱动·camera·v4l2
极客代码3 个月前
【Linux】【字符设备驱动】深入解析
linux·驱动开发·unix·驱动·字符设备驱动
沐多3 个月前
linux模拟HID USB设备及wireshark USB抓包配置
驱动
极客代码3 个月前
【Linux】内核驱动模块
linux·内核·内核模块·unix·驱动
郁大锤3 个月前
linux alsa-lib snd_pcm_open函数源码分析(四)
linux·音频·pcm·源码分析·驱动·alsa
昵称p3 个月前
如何解决不能将开发板连接到虚拟机的问题(连接显示灰色,不能选中)
驱动·虚拟机无法连接开发板
小仇学长3 个月前
Linux内核编程(十九)SPI子系统一驱动MCP2515(SPI转CAN模块)
linux·驱动·spi·mcp2515