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)

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

相关推荐
郁大锤3 天前
linux alsa-lib snd_pcm_open函数源码分析(四)
linux·音频·pcm·源码分析·驱动·alsa
昵称p9 天前
如何解决不能将开发板连接到虚拟机的问题(连接显示灰色,不能选中)
驱动·虚拟机无法连接开发板
小仇学长13 天前
Linux内核编程(十九)SPI子系统一驱动MCP2515(SPI转CAN模块)
linux·驱动·spi·mcp2515
通俗_易懂22 天前
44-RK3588s调试 camera-engine-rkaiq(rkaiq_3A_server)
人工智能·计算机视觉·rk3588·驱动·camera·imx415
肖爱Kun22 天前
LINUX IIC总线驱动-设备框架
驱动
网络研究院1 个月前
微软的 Drasi:一种轻量级的事件驱动编程方法
microsoft·微软·编程·驱动·事件·轻量级·drasi
肖爱Kun1 个月前
内核定时器API实现点灯
驱动
肖爱Kun1 个月前
正点原子linux驱动笔记-字符设备驱动
驱动
kpler2 个月前
gpio子系统
c语言·驱动
黄卷青灯772 个月前
电脑驱动作用详解
linux·数据库·电脑·驱动